2

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

 2 years ago
source link: https://segmentfault.com/a/1190000040894120
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

利用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"

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK