乐趣区

关于husky:利用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、失败返回 非 0 Node.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"
退出移动版