为了方便项目打包,我用Node写了个git-tag工具
创始人
2024-05-16 09:31:33

目录

前言

实现过程

起步

npm全局命令

功能实现

功能验证

代码发布

写在最后


前言

在使用git执行打包操作时,我们常常会根据场景在tag中增加一些标识。

以基准版本为1.0.0为例:软件开发初期可以定义1.0.0-alpha.0,开发阶段是1.0.0-beta.0,上预发布环境时可以打成1.0.0-release.0,最终上线可以打v1.0.0。

也许每个公司都有一套标准,是否能做一个工具适配这样的场景?

于是就有了这篇文章,我想借这篇文章与大家分享一下最近整的一个git标签工具git-tag-sh

实现过程

起步

在开始前,先分享一下我平时打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 = {// 校验有无githasGit: 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进行包发布

写在最后

感谢你看到了最后,如果文章有帮助,还请支持一下,感谢!

源码:git-tag-sh: 针对当前项目执行 git 打包操作

npm:git-tag-sh - npm

相关内容

热门资讯

猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
少数民族的传统节日有哪些 55... 公务员考试常识判断模块考查范围广泛,需要大家在日常多加积累,本文小编总结了中国少数民族的传统节日,希...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
mb什么意思 MB和Mb 相信很多人都还不太清楚手机流量的统计单位,经常听说谁谁流量包月5个G。其实G、GB、KB、M和MB数...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...