我因为在写 cli 工具的时候,大量的使用各种 npm 包,近期把我认为好玩的一些包分享出来。纯属自己的一些见解,有什么建议可以私底下沟通。
今天要我想重点介绍的就是simple-git
,simple-git
是一个用于在 Node.js 中简化 Git 命令操作的轻量级库。它提供了一种简单的接口来执行常见的 Git 操作,而无需直接使用命令行。
为什么要用他?
- 优化提交效率:因为我在写PLUS团队CLI工具的时候有一个场景,就是代码的提交发布,这个动作在开发中非常频繁,正常的提交我们都需要有很多步骤如
git add
,git commit,
和git push
,其实这些步骤是可以简化和可视化的,直接一个xxx push
就能拿完成上面三个动作 - 标准化
commit
信息:通过对simple-git
的方法的二次封装,可以轻松实现提交信息的格式化,确保每次提交都能提供清晰、有条理的信息,这对于维护代码库的整洁性和可读性大有裨益 - 直接跟 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 大模型对代码的提交进行代码审核