使用 simple-git 在 Node.js 中高效调用 Git 命令

我因为在写 cli 工具的时候,大量的使用各种 npm 包,近期把我认为好玩的一些包分享出来。纯属自己的一些见解,有什么建议可以私底下沟通。

今天要我想重点介绍的就是simple-gitsimple-git是一个用于在 Node.js 中简化 Git 命令操作的轻量级库。它提供了一种简单的接口来执行常见的 Git 操作,而无需直接使用命令行。

为什么要用他?

  1. 优化提交效率:因为我在写PLUS团队CLI工具的时候有一个场景,就是代码的提交发布,这个动作在开发中非常频繁,正常的提交我们都需要有很多步骤如 git add, git commit,git push,其实这些步骤是可以简化和可视化的,直接一个xxx push 就能拿完成上面三个动作
  2. 标准化commit信息:通过对simple-git的方法的二次封装,可以轻松实现提交信息的格式化,确保每次提交都能提供清晰、有条理的信息,这对于维护代码库的整洁性和可读性大有裨益
  3. 直接跟 CI/CD打通:很多团队在触发公司流水线的时候还在通过手动或者提交代码,但是这两个动作都有一些问题,手动提交效率低,直接提交代码就是太随意不是所有提交都需要去触发流水线,可以通过对 git 一些动作去选择性触发流水线

综上所述:simple-git 不仅通过其简洁的接口降低了使用 Git 的复杂性,还通过优化提交低了使用 Git 的复杂性,还通过优化提交流程和促进提交信息标准化。

安装方式

我比较喜欢pnpm相对于npm yarn 更加便捷和快速

shell 复制代码
pnpm install simple-git
npm install simple-git

基础的方法封装

  • 获取分支:获取当前分支gitPath是当前用户的操作目录
js 复制代码
const simpleGit = require('simple-git');

const getBranchName = async (gitPath) => {
  return await simpleGit(gitPath)
    .branch()
    .then((branch) => {
      return branch.current || null;
    })
    .catch((err) => {
      console.log(err);
      return null;
    });
};
  • 拉取分支代码:拉取分支代码,并且输出一些信息,gitPath 是当前用户的操作目录,branchName是拉取那个分支名,其中我对拉取的信息做了一个表格输出cli-table,后续有人感兴趣我可以讲下这个包的使用
js 复制代码
const Table = require('cli-table');//也是一个很好玩的包,支持各种表格信息输出

//表格的边框样式
const chars = {
  top: '═',
  'top-mid': '╤',
  'top-left': '╔',
  'top-right': '╗',
  bottom: '═',
  'bottom-mid': '╧',
  'bottom-left': '╚',
  'bottom-right': '╝',
  left: '║',
  'left-mid': '╟',
  mid: '─',
  'mid-mid': '┼',
  right: '║',
  'right-mid': '╢',
  middle: '│'
};


const pullBranch = async (gitPath, branchName) => {
  return await simpleGit(gitPath)
    .exec(() => {
      //对 log 做了一层封装可以用 console.log 代替
      log.info(`《${branchName}》分支 开始拉取...`);
    })
    .pull('origin', branchName)
    .then((update) => {
      if (update) {
        log.info(`《${branchName}》分支 拉取信息`);

        var headArr = Object.keys(update.summary);
        var valArr = Object.values(update.summary);
        var table = new Table({
          head: headArr,
          colWidths: [30, 30, 30],
          chars: chars
        });
        table.push(valArr);
        console.log(chalk.greenBright(table.toString()));
        log.success(`《${branchName}》分支 拉取成功`);
        console.log('\n');
      }
      return true;
    })
    .catch((e) => {
      console.log(e);
      log.error(`《拉取线上分支${branchName}》异常`);

      return false;
    });
};
  • 提交信息并且 pus分支,这是把两个动作合并成一个动作,commit 和 push
js 复制代码
const commitPushBranch = async (gitPath, pushBranch, commitMessage) => {
  return await simpleGit(gitPath)
    .commit(commitMessage)
    .push('origin', pushBranch)
    .then((status) => {
      log.success(`${pushBranch}代码提交成功`);
      return true;
    })
    .catch((e) => {
      log.error(e.message);
      return false;
    });
};
  • 切换分支
js 复制代码
const checkBranch = async (gitPath, branch) => {
  return await simpleGit(gitPath)
    .checkout(branch)
    .then((st) => {
      log.info(st);
      return true;
    })
    .catch((e) => {
      log.error(e);

      return false;
    });
};
  • 合并分支,注意异常我会切到用户当前的分支
js 复制代码
const mergeBranch = async (gitPath, currentBranch, targetBranch) => {
  return await simpleGit(gitPath)
    .mergeFromTo(currentBranch, targetBranch)
    .then((status) => {
      log.success(`《${targetBranch}》合并《${currentBranch}》分支成功`);
      return true;
    })
    .catch((e) => {
      log.error(e);
      simpleGit(gitPath).checkout(currentBranch);
      return false;
    });
};
  • push 分支
js 复制代码
const pushBranch = async (gitPath, branch, currentBranch, successMsg) => {
  await simpleGit(gitPath)
    .push('origin', branch)
    .then((status) => {
      log.success(successMsg);
    })
    .catch((e) => {
      log.error(e.message);
      simpleGit(gitPath).checkout(currentBranch);
    });
};
  • 删除远程分支
js 复制代码
// 删除远程分支用
const deleteOriginBranch = async (gitPath, branchName) => {
  await simpleGit(gitPath)
    .push('origin', [branchName, '--delete'])
    .then((status) => {
      log.success(`已删除远程分支${branchName}`);
    })
    .catch((e) => {
      log.error(`删除远程分支${branchName}失败`);
      log.error(e.message);
    });
};

跟 CLI 结合或者说跟业务结合

我还是按照前面提到的三个功能

  • 优化提交效率:我在写 cli 的时候会有一个提交命令,大体意思就是你在终端运行xxx deploy,具体注册 cli 的命令我这就不细讲,只讲git提交相关部分
js 复制代码
//这是注册的命令 
program
  .command('deploy')
  .description('把代码按照部署规则提交到git仓库,触发自动部署的功能')
  .alias('d')
  .action(function () {
    const packageName = '@xxx-cli/deploy';
    const cmd = arguments[arguments.length - 1];
    const cmdName = cmd._name;
    exec(
      {
        cmdName,
        packageName
      },
      cmd
    );
  });

//开启部署动作
async runDeploy() {
  //第一步:让你选择你要把你的代码部署到那个环境
  const { deployEnv } = await prompts({
    type: 'select',
    name: 'deployEnv',
    message:
      'Pick  请选择部署的环境:(正常开发顺序是 beta -> pretest -> production )',
    choices: [
      {
        title: 'beta',
        value: 'beta',
        description:
          '部署预发环境(打预发包,相当于我们在预发环境开发,上线前有大量的bug还需要修改)'
      },
      {
        title: 'pretest',
        value: 'pretest',
        description:
          '部署预上线环境(打线上包,此功能主要是打包线上包发布到预发)'
      },
      {
        title: 'production',
        value: 'production',
        description:
          '直接部署上线(把pretest的最后版本合并到master触发上线审批流程)'
      }
    ]
  });
  //选择不同的环境进行不同代码的提交流程
  switch (deployEnv) {
    case 'beta':
      this.deployBeta();
      break;
    case 'pretest':
      this.deployPretest();
      break;
    case 'production':
      this.deployProduction();
      break;
    default:
      log.error(`未识别的环境,请联系开发人员`);
      break;
  }
}

//此处只讲部署到预发,通过这一个方法,把代码提交,信息填写,和触发流水线整合在一起
async deployBeta() {

  // 预发的环境的特有分支  这次用来标准化,只有发布到这个分支才会触发流水线
  const branch = 'xxx-beta';

  // 获取所在分支
  const currentBranch = await getBranchName(this.userRoot);

  // 其中不准许对 master 进行操作 主要用来规范化
  if (currentBranch && currentBranch !== 'master' && currentBranch !== branch) {
    // 提示是否拉取本分支的线上代码
    const { needPullCurrentBranch } = await prompts({
      type: 'toggle',
      name: 'needPullCurrentBranch',
      message: `是否拉取《${currentBranch}》分支的线上代码?`,
      initial: true,
      active: 'yes',
      inactive: 'no'
    });

    // 拉取线上的开发中的分支代码  默认是不拉取的
    if (needPullCurrentBranch) {
      const needContinue = await pullBranch(this.userRoot, currentBranch);

      // 判断拉取失败了  就退出程序不往下走了
      !needContinue && process.exit();
    }

    // 本地提交的status
    await getStatus(this.userRoot);

    // 选择提交功能
    const { commitType } = await prompts({
      type: 'select',
      name: 'commitType',
      message: '请选择提交类型',
      //此处做了终端复选框 让用户直接选择信息并且填写
                choices: [
                    {
                        value: 'feat',
                        title: 'feat',
                        description: '特性(feat):新功能'
                    },
                    {
                        value: 'fix',
                        title: 'fix',
                        description: '修复(fix):修复'
                    },
                    {
                        value: 'refactor',
                        title: 'refactor',
                        description: '重构(refactor): 重构(既不是增加feature,也不是修复bug)'
                    },
                    {
                        value: 'perf',
                        title: 'perf',
                        description: '性能(perf):性能优化'
                    },
                    {
                        value: 'docs',
                        title: 'docs',
                        description: '文档(docs):文档变更'
                    },
                    {
                        value: 'style',
                        title: 'style',
                        description: '格式(style):代码格式(不影响代码运行的变动)'
                    },
                    {
                        value: 'chore',
                        title: 'chore',
                        description: '日常(chore):构建过程或辅助工具的变动'
                    },
                    {
                        value: 'revert',
                        title: 'revert',
                        description: '回滚(revert):代码回退'
                    },
                    {
                        value: 'test',
                        title: 'test',
                        description: '测试(test):增加测试'
                    }
                ] 
            });

            // 用户取消选择
            !commitType && process.exit();

            // 输出commit信息  约束提交信息
            const { commitMessage } = await prompts({
                type: 'text',
                name: 'commitMessage',
                message: `请输入本次提交的信息`,
                validate: (v) => {
                    const minLength = 3;
                    if (v.length > minLength) {
                        return true;
                    } else {
                        return `请输入描述文字的长度大于${minLength}`;
                    }
                }
            });

            // 用户取消选择
            !commitMessage && process.exit();

            // 最后核实是否提交
            const { needCommit } = await prompts({
                type: 'toggle',
                name: 'needCommit',
                message: `请确认是否提交?`,
                initial: true,
                active: 'yes',
                inactive: 'no'
            });

            // 判断是否取消
            if (!needCommit) {
                log.info('取消提交');
                process.exit();
            }

            // 提交开发代码
            const hasPush = await commitPushBranch(
                this.userRoot,
                currentBranch,
                `${commitType}:${commitMessage}(xxx-beta)`
            );

            // 判断是否取消
            if (!hasPush) {
                log.info('push分支失败,退出提交流程');
                process.exit();
            }

            // 拉取线上部署分支代码
            const pullDone = await pullBranch(this.userRoot, branch);

            // 切换到merge分支
            pullDone && (await checkBranch(this.userRoot, branch));

            //  从部署分支合并开发分支的代码
            const isMerge = await mergeBranch(this.userRoot, currentBranch, branch);

            //如果合并失败立刻切回开发分支
            !isMerge && (await checkBranch(this.userRoot, currentBranch));

            // 提交merge分支代码
            isMerge &&
                (await pushBranch(
                    this.userRoot,
                    branch,
                    currentBranch,
                    `《${branch}》代码提交成功,即将触发自动发布预发流程,最终结果以斑布发布流程为准`
                ));

            // 切会原有开发分支
            await checkBranch(this.userRoot, currentBranch);
        } else {
            currentBranch == 'master'
                ? log.error(`master分支受保护,请不要在master分支执行部署命令`)
                : log.error(
                      `无法获取到你的开发分支,请关联git和确定git相关的分支,或者你所在的分支在《${branch}》受保护的分支上`
                  );

            process.exit();
        }
    }
  • 标准化commit信息,在上述代码中里面有一个动作是让你选择你要提交的内容是什么,还有对内容进行了一些限制,这块其实还可以做更加严格的封装, 其中 commitType 是提交信息前部分内容,commitMessage是提交信息的主体内容,我还在最后加了上我有 cli 工具带上的特有标识符xxx-beta,这样就可以识别那些信息是有 cli 提交上来,这样可以在流水线上对不规范的非 cli 提交的内容进行过滤不给打包
js 复制代码
// 选择提交功能
const { commitType } = await prompts({
  type: 'select',
  name: 'commitType',
  message: '请选择提交类型',
  //此处做了终端复选框 让用户直接选择信息并且填写
  choices: [
    {
      value: 'feat',
      title: 'feat',
      description: '特性(feat):新功能'
    },
    {
      value: 'fix',
      title: 'fix',
      description: '修复(fix):修复'
    },
    {
      value: 'refactor',
      title: 'refactor',
      description: '重构(refactor): 重构(既不是增加feature,也不是修复bug)'
    },
    {
      value: 'perf',
      title: 'perf',
      description: '性能(perf):性能优化'
    },
    {
      value: 'docs',
      title: 'docs',
      description: '文档(docs):文档变更'
    },
    {
      value: 'style',
      title: 'style',
      description: '格式(style):代码格式(不影响代码运行的变动)'
    },
    {
      value: 'chore',
      title: 'chore',
      description: '日常(chore):构建过程或辅助工具的变动'
    },
    {
      value: 'revert',
      title: 'revert',
      description: '回滚(revert):代码回退'
    },
    {
      value: 'test',
      title: 'test',
      description: '测试(test):增加测试'
    }
  ] 
});

// 用户取消选择
!commitType && process.exit();

// 输出commit信息  约束提交信息
const { commitMessage } = await prompts({
  type: 'text',
  name: 'commitMessage',
  message: `请输入本次提交的信息`,
  validate: (v) => {
    const minLength = 3;
    if (v.length > minLength) {
      return true;
    } else {
      return `请输入描述文字的长度大于${minLength}`;
    }
  }
});
  • 直接跟 CI/CD打通:前面的代码都已经有,就是只有当通过 cli 工具进行提交并且带有特殊标识信息的提交我们才会走 CI/CD的操作,避免一些非规范性的提交

总结:

近期在一个项目里面,我觉得其实可以基于 simple-git 在 cli 代码提交的时候跟 gpt 进行结合,再合并分支的时候通过大模型进行审核和优化建议,如果再牛逼一点其实可以直接去操作代码进行修改,主要灵感来自同事聊天,去年跟同事合作的过程中发现他在用 node+ gtp 大模型对代码的提交进行代码审核

相关推荐
无双_Joney18 分钟前
[更新迭代 - 1] Nestjs 在24年底更新了啥?(功能篇)
前端·后端·nestjs
在云端易逍遥20 分钟前
前端必学的 CSS Grid 布局体系
前端·css
EMT20 分钟前
在 Vue 项目中使用 URL Query 保存和恢复搜索条件
javascript·vue.js
ccnocare21 分钟前
选择文件夹路径
前端
艾小码22 分钟前
还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!
前端·javascript·react.js
闰五月23 分钟前
JavaScript作用域与作用域链详解
前端·面试
泉城老铁26 分钟前
idea 优化卡顿
前端·后端·敏捷开发
前端康师傅26 分钟前
JavaScript 作用域常见问题及解决方案
前端·javascript
司宸28 分钟前
Prompt结构化输出:从入门到精通的系统指南
前端