前言
在使用git执行打包操作时,我们常常会根据场景在tag中增加一些标识。
以基准版本为1.0.0为例:软件开发初期可以定义1.0.0-alpha.0,开发阶段是1.0.0-beta.0,上预发布环境时可以打成1.0.0-release.0,最终上线可以打v1.0.0。
也许每个公司都有一套标准,是否能做一个工具适配这样的场景?
于是就有了这篇文章,我想借这篇文章与大家分享一下最近整的一个git标签工具
实现过程
起步
在开始前,先分享一下我平时打tag的命令:
在代码commit和push完后,以当前版本1.0.0为例,先查询是否已有同名版本
git tag -l "1.0.0"
执行tag命令
git tag 1.0.0
push tag操作
git push origin 1.0.0
执行完成后,tag包就会触发CI/CD的构建功能,部署,发布
如果使用命令来操作,可能就是git-tag-sh,完事
那么思路有了,接下来就是实现了
npm全局命令
我们在使用一些全局的npm依赖时,如cross-env,pnpm或一些cli时,时常能在node全局依赖的目录下看到 .cmd .ps1 的命令文件,便于在终端直接调用。
如何新建一个npm全局包?
流程有三步:
1.新建命令文件,在文件顶部新增 #!/usr/bin/env node
如:
#!/usr/bin/env node console.log('hello world');
2.在package.json中新增bin属性,关联这个文件
3.运行npm link命令,将命令链接到全局npm目录下
此时执行hello-world时会直接执行index.js
功能实现
helpers.shell:命令函数
const { exec } = require("child_process"); const { defer } = require("utils-lib-js"); // shell命令封装 exports.shell = (__shell, showLog = true, opts = {}) => { const { resolve, reject, promise } = defer(); exec(__shell, opts, (err, str, errStr) => { const __err = err ?? errStr; showLog && console.info(`>>>>> ${__shell}: `, __err || str); if (__err) reject(__err); else resolve(str); }); return promise; };
helpers.git:执行git操作的函数
const { shell } = require("./helpers.shell"); const { catchAwait } = require("utils-lib-js"); // git操作 exports.git = { // 校验有无git hasGit: async () => catchAwait(shell("git --version", false)), // 检测标签是否已存在 hasTag: async (tag) => { const [err, tags] = await catchAwait(shell(`git tag -l "${tag}"`, false)); const len = tags?.length > 0; if (err ?? len) return err ?? "标签已存在"; }, tag: async (tag, message) => { const [err] = await catchAwait( shell(`git tag -a ${tag} -m "${message}"`, false) ); if (err) return err; }, push: async (tag) => { const [err] = await catchAwait(shell(`git push origin ${tag}`, false)); const succ = err.includes(tag); if (err && !succ) return err; }, };
helpers.tag:tag模板函数
const { git } = require("./helpers.git"); // git-tag exports.gitTag = async ({ tag, message }) => { console.log(`git-tag-start-------------`, tag); message && console.log(`git-tag-message-------------`, message); const err = await git.hasTag(tag); if (err) return err; const err2 = await git.tag(tag, message); if (err2) return err2; const err3 = await git.push(tag); if (err3) return err3; console.log(`git-tag-success-------------`, tag); };
helpers.current:对当前项目的操作
// 获取当前文件夹路径 const getCurrentDir = () => process.cwd(); // 获取文件内容 const getFile = (dir, path) => require(`${dir}${path}`); // 获取package文件内容 exports.currentPackage = getFile(getCurrentDir(), "/package.json"); exports.getFile = getFile; exports.getCurrentDir = getCurrentDir;
helpers.others:其他函数封装
// 缩写一下reject函数 const rej = (err = "") => Promise.reject(err); // 缩写一下resolve函数 const res = (result = "") => Promise.resolve(result); // 对象转数组 const Obj2Arr = (obj = {}) => Reflect.ownKeys(obj).map((it) => obj[it]); // 首字母转小写 const first2Lower = (str = "") => str.substring(0, 1).toLowerCase() + str.substring(1); // 计数器,计算对象中有几个whiteList(白名单)的值(whiteList代表校验哪些key),maxCont表示最大计算到几个为止提前跳出循环 function countArgs(opts, whiteList = [], maxCont, count = 0) { for (const it of whiteList) { if (maxCont === count) return maxCont; !!opts[it] && count++; } return count; } module.exports = { res, rej, Obj2Arr, first2Lower, countArgs, };
helpers.command:主函数
const { program } = require("commander"); const { options, prodKey, splitKey } = require("./helpers.config"); const { getType } = require("utils-lib-js"); const { rej, Obj2Arr, first2Lower, countArgs } = require("./helpers.others"); const { currentPackage } = require("./helpers.current"); const { gitTag } = require("./helpers.tag"); const { git } = require("./helpers.git"); const { version, name, description } = require("../package.json"); // 初始化命令函数 exports.initCmd = async () => { const [err] = await git.hasGit(); if (err) return rej(err); programOptions(options, program) .name(name) .version(version) .description(description) .parse(process.argv); const err1 = await checkOptions(program); if (err1) return rej(err1); const { tag, message } = executeCommand(program); const err2 = await gitTag({ tag, message }); if (err2) return rej(err2); }; // 批量添加命令 function programOptions(config, program) { config.forEach((it) => program.option(...Obj2Arr(it))); return program; } // 校验命令 function checkOptions(program) { const opts = program.opts(); if (countArgs(opts, ["Alpha", "Beta", "Release"]) > 1) return "只能选择一个后缀,请修改后再操作"; } // 执行命令 function executeCommand(program) { const opts = program.opts(); let message = ""; let tagVersion = currentPackage.version; let mixStr = ""; Reflect.ownKeys(opts).forEach((key) => { const it = opts[key]; const isTypeIsStr = getType(it) === "string"; switch (key) { case "Production": const __prodKey = isTypeIsStr ? it : prodKey; tagVersion = `${__prodKey}${tagVersion}`; break; case "CurVer": console.log(currentPackage.version); break; case "Suffix": if (!isTypeIsStr) break; if (it.startsWith(splitKey)) { mixStr = `${it}`; } else { mixStr = `${splitKey}${it}`; } break; case "Alpha": case "Beta": case "Release": tagVersion = `${tagVersion}${splitKey}${first2Lower(key)}`; break; case "Message": isTypeIsStr && (message = it); break; } }); return { tag: tagVersion + mixStr, message }; }
helpers.config:配置文件
exports.options = [ { flags: "-cv, -curVer", description: "获取当前目录下程序版本号(Get the program version number in the current directory)", }, { flags: "-p, -production [string]", description: "是否打'生产环境'标签(Whether to label the 'production' environment)", }, { flags: "-s, -suffix [string]", description: "增加标签后缀(Add tag suffix)", }, { flags: "-m, -message [string]", description: "增加标签提交信息(Add tag submission information)", }, { flags: "-a, -alpha", description: "增加'alpha'后缀,标识为软件开发初期包(Add the suffix 'alpha' to identify the initial package of software development)", }, { flags: "-b, -beta", description: "增加'beta'后缀,标识为软件开发中期包(Add the suffix 'beta' and mark it as software development interim package)", }, { flags: "-r, -release", description: "增加'release'后缀,标识为软件开发完成包(Add the suffix 'release' to identify the software development completion package)", }, ]; exports.prodKey = "v"; // 生产环境标识 exports.splitKey = "-"; // tag后缀分隔符
最后在命令文件中增加以下代码
#!/usr/bin/env node const { initCmd } = require("../utils/helpers.command"); initCmd().catch(console.error);
功能验证
使用 npm link 进行本地调试(如果全局已经安装了这个包时,使用npm link会提示安装失败,此时需要卸载全局包,或者本地命令换个名字)
使用 git-tag-sh 打默认开发包
git-tag-sh -p 打正式包
git-tag-sh -b 打测试包
git-tag-sh -s test.0 自定义后缀
git-tag-sh -r -m 上预发布环境 打预发布包带信息
最后试试多个场景打包 git-tag-sh -s test.0 -b -m 需求提测 -p b
远程的效果
代码发布
完成上述代码及验证后使用npm publish进行包的发布,如果没有npm账号的话可以进行下面几步操作
在npm注册账号,在项目中打开终端执行 npm adduser 增加账户,npm login 登录账户,并输入用户名密码,最后执行npm publish进行包发布
写在最后
感谢你看到了最后,如果文章有帮助,还请支持一下,感谢!