利用husky实现前端项目自定义规范校验

提交Bug修复时容易忘记同步调整版本号,如果遗漏了很容易造成漏更新等问题,所以打算将版本号修改做成硬性的检测规范。

实现效果跟ESlint的检测差不多,Git提交后自动执行代码检测,不符合规范就报错撤回提交,符合规范就通过提交。

分析

主要要解决以下几个问题:

  • 触发检测的方式

    既然想到ESlint,那第一个念头是给ESlint增加自定义插件。但仔细又想了想,因为检测的是非JavaScript文件,而且也不是代码那种逻辑检测,只是在提交前做一下相应的文件是否有修改,实际上并不是很适合的场景。

    最适合的还是直接用Git的钩子,ESlint就是利用husky在相关钩子中调用检测。

    之前写了篇husky7 + commitlint + lint-staged 记录,所以流程比较熟悉,只需要在.husky文件夹下添加相关钩子名称的文件,然后直接写shell脚本就能出发。

    单纯的脚本并不利于维护,所以打算跟ESlint一样,写成命令行式的,也方便后期增加功能。

  • 怎样知晓文件变动

    这个就是Git自带的功能了,只需要利用git diff,对比package.json文件,就会输出对比信息。

    但文本的不好分析,一下子又没找到相关可用的插件,于是用peggy自己写了个相关的解析器,将文本转换成方便解析的json数据。

  • 提交失败后终止提交

    shell脚本成功返回0、失败返回非0Node.js提供了相关方法process.exit(1)

    如果有不懂的方式实际上翻一翻lint-staged都能找到所需的答案,毕竟现成的例子在那里。

  • 提供可以主动绕过提交的方式

    既然是加入钩子中,不需要的时候肯定不能删了钩子再提交。那只能在提交逻辑中做跳过检测。

    最合适的是在提交消息的body中携带指定的关键字,当程序识别到关键字就跳过检测逻辑即可。

关键逻辑

提供命令行调用命令

创建一个空项目,添加bin字段,为我们的程序增加相关的调用命令

"bin":{
    "check": "./dist/index.js"
}

对应的check是命令行的调用名称,对应的值是要执行的JavaScript文件:

#!/usr/bin/env node
console.log('hello')

这样就能打印出hello了。

当然这样还不够,命令行程序还可能携带参数,或是其他功能,所以可以配合Commander.js辅助处理命令行命令。

由于和业务代码写在一起,只能提供大概的示例:

import { Command } from "commander";

const program = new Command();
program
    .command("change")
    .option(
      "--commitMsgPath <filePath>",
      "当 commit message 中包含指定字符串时跳过当前命令检测"
    )
    .description("检测版本号是否有变动")
    .action(async ({ commitMsgPath }: { commitMsgPath: string }) => {
      try {
        let msg: string[];
        if (commitMsgPath) msg = readGitMessage(commitMsgPath);

        const flag = await checkVersion(msg);
        if (!flag) throw new Error("请修改版本号再提交");
      } catch (e) {
        console.error(`${DATA.NAME}: ${e.name} ${e.message}`);
        process.exit(1);
      }
    });
    
program.parse(process.argv);

调用git diff判断数据

执行git命令可以使用simple-git这个库,功能很丰富,但可惜diff的返回数据是纯文本,并不方便处理。利用之前写好的简单的diff格式的解析器,处理成可读的json数据。

剩下的就很简单了,将返回的diff数据传入解析器,然后判断解析器中是否有相应的关键字version,有即代表版号是有修改过的,有需要可以做更细致的版本号校验。

import simpleGit, { SimpleGit } from "simple-git";
const GitDiffParser = require("../lib/gitDiffParser");

/**
 * 检测版本号是否变动
 * @param msg
 */
export const checkVersion = async (msg?: string[]) => {
  let flag = false;

  const git: SimpleGit = simpleGit({
    baseDir: process.cwd(),
    binary: "git",
  });

  // 检测 git message 中是否有指定文本 有则跳过版本检测
  if (msg && msg.some((item) => item === DATA.SKIP_MESSAGE_KEY)) return true;

  // 判断版本号是否有变化
  const diff = await git.diff(["--cached", "package.json"]);
  if (diff) {
    const result = <GitDiffParserResult>(
      GitDiffParser.parse(diff, { REMOVE_INDENT: true })
    );

    result.change.forEach((item) => {
      item.content.forEach((content) => {
        if (content.type === "+" && content.text.includes(`"version"`))
          flag = true;
      });
    });
    return flag;
  }
  return flag;
};

检测message提供跳过检测的方式

本来是写在pre-commit钩子中的,但查了几个获取消息的git命令,都没办法获取到当前的message

所以只能换到commit-msg钩子中,通过$1变量来获取到消息。而且传回的并不是消息的文本,而是消息的文件路径,所以需要自行读取文件内容。

/**
 * 读取git message消息文件
 */
export const readGitMessage = (filePath: string) => {
  try {
    const msg: string = fs.readFileSync(
      path.join(process.cwd(), filePath),
      "utf-8"
    );
    return msg.split(/\s/).filter((item) => item);
  } catch (e) {
    return undefined;
  }
};

这样在checkVersion流程中调用,跟预定的key对比,如果相同就直接返回true,这样就跳过了版本检测逻辑了。

commit-msg脚本

添加进项目,就能直接通过npx调起命令,执行检测逻辑。而消息地址作为参数传入命令中(之前的.option("--commitMsgPath <filePath>", "当 commit message 中包含指定字符串时跳过当前命令检测")配置的参数)。

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx commitlint --edit "$1"
npx check change --commitMsgPath "$1"

01小径
在路上,遇见了一只BUG,我将它抓住,收藏在了这里。<( ̄︶ ̄)>
651 声望
14 粉丝
0 条评论
推荐阅读
Theia 开发环境搭建
Node.js &gt;= 16.14.0 and &lt; 17.If you are interested in Theia's VS Code Extension support then you should use a Node version at least compatible with the one included in the version of Electron ...

LnEoi1阅读 180

正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青57阅读 8.7k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy49阅读 7.4k评论 12

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 7k评论 12

封面图
CSS 绘制一只思否猫
欢迎关注我的公众号:前端侦探练习 CSS 有一个比较有趣的方式,就是发挥想象,绘制各式各样的图案,比如来绘制一只思否猫?思否猫,SegmentFault 思否的吉祥物,是一只独一无二、特立独行、热爱自由的(&gt;^ω^&lt...

XboxYan48阅读 3.4k评论 14

封面图
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs32阅读 3.6k评论 5

封面图
还在用 JS 做节流吗?CSS 也可以防止按钮重复点击
举个例子:一个保存按钮,为了避免重复提交或者服务器考虑,往往需要对点击行为做一定的限制,比如只允许每300ms提交一次,这时候我想大部分同学都会到网上直接拷贝一段throttle函数,或者直接引用lodash工具库

XboxYan35阅读 2.8k评论 2

封面图
651 声望
14 粉丝
宣传栏