项目一键添加husky

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

项目一键添加husky

Yomuki   2022-09-29 我要评论

关于husky

前置条件:项目已关联了git。

husky有什么用?

当我们commit message时,可以进行测试和lint操作,保证仓库里的代码是优雅的。 当我们进行commit操作时,会触发pre-commit,在此阶段,可进行test和lint。其后,会触发commit-msg,对commit的message内容进行验证。

pre-commit

一般的lint会全局扫描,但是在此阶段,我们仅需要对暂存区的代码进行lint即可。所以使用lint-staged插件。

commit-msg

在此阶段,可用 @commitlint/cli @commitlint/config-conventional 对提交信息进行验证。但是记信息格式规范真的太太太太麻烦了,所以可用 commitizen cz-git 生成提交信息。

一键添加husky

从上述说明中,可以得出husky配置的基本流程:

  • 安装husky;安装lint-staged @commitlint/cli @commitlint/config-conventional commitizen cz-git
  • 写commitlint和lint-staged的配置文件
  • 修改package.json中的scripts和config
  • 添加pre-commit和commit-msg钩子

看上去简简单单轻轻松松,那么,开干!

先把用到的import拿出来溜溜

import { red, cyan, green } from "kolorist"; // 打印颜色文字
import { copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { cwd } from "node:process";
import prompts from "prompts";// 命令行交互提示
import { fileURLToPath } from "node:url";
import { getLintStagedOption } from "./src/index.js";// 获取lint-staged配置 ,后头说
import { createSpinner } from "nanospinner"; // 载入动画(用于安装依赖的时候)
import { exec } from "node:child_process";

package验证

既然是为项目添加,那当然得有package.json文件啦!

const projectDirectory = cwd();
const pakFile = resolve(projectDirectory, "package.json");
if (!existsSync(pakFile)) {
    console.log(red("未在当前目录中找到package.json,请在项目根目录下运行哦~"));
    return;
}

既然需要lint,那当然也要eslint/prettier/stylelint啦~

const pakContent = JSON.parse(readFileSync(pakFile));
const devs = {
    ...(pakContent?.devDependencies || {}),
    ...(pakContent?.dependencies || {}),
};
const pakHasLint = needDependencies.filter((item) => {
    return item in devs;
});

但是考虑到有可能lint安装在了全局,所以这边就不直接return了,而是向questions中插入一些询问来确定到底安装了哪些lint。

const noLintQuestions = [
	{
		type: "confirm",
		name: "isContinue",
		message: "未在package.json中找到eslint/prettier/stylelint,是否继续?",
	},
	{
		// 处理上一步的确认值。如果用户没同意,抛出异常。同意了就继续
		type: (_, { isContinue } = {}) => {
			if (isContinue === false) {
				throw new Error(red("✖ 取消操作"));
			}
			return null;
		},
		name: "isContinueChecker",
	},
	{
		type: "multiselect",
		name: "selectLint",
		message: "请选择已安装的依赖:",
		choices: [
			{
				title: "eslint",
				value: "eslint",
			},
			{
				title: "prettier",
				value: "prettier",
			},
			{
				title: "stylelint",
				value: "stylelint",
			},
		],
	},
];
const questions = pakHasLint.length === 0 ? [...noLintQuestions, ...huskyQuestions] : huskyQuestions; // huskyQuestions的husky安装的询问语句,下面会讲

husky安装询问

因为不同的包管理器有不同的安装命令,以及有些项目会不需要commit msg验证。所有就会有以下询问的出现啦

const huskyQuestions = [
	{
		type: "select",
		name: "manager",
		message: "请选择包管理器:",
		choices: [
			{
				title: "npm",
				value: "npm",
			},
			{
				title: "yarn1",
				value: "yarn1",
			},
			{
				title: "yarn2+",
				value: "yarn2",
			},
			{
				title: "pnpm",
				value: "pnpm",
			},
			{
				title: "pnpm 根工作区",
				value: "pnpmw",
			},
		],
	},
	{
		type: "confirm",
		name: "commitlint",
		message: "是否需要commit信息验证?",
	},
];

使用prompts进行交互提示

let result = {};
try {
  result = await prompts(questions, {
      onCancel: () => {
      throw new Error(red("❌Bye~"));
    },
  });
} catch (cancelled) {
  console.log(cancelled.message);
  return;
}
const { selectLint, manager, commitlint } = result;

这样子,我们就获取到了:

  • manager 项目使用的包管理
  • commitlint 是否需要commit msg验证
  • selectLint 用户自己选择的已安装的lint依赖

生成命令

通过manager和commitlint,可以生成要运行的命令

const huskyCommandMap = {
  npm: "npx husky-init && npm install && npm install --save-dev ",
  yarn1: "npx husky-init && yarn && yarn add --dev ",
  yarn2: "yarn dlx husky-init --yarn2 && yarn && yarn add --dev ",
  pnpm: "pnpm dlx husky-init && pnpm install && pnpm install --save-dev ",
  pnpmw: "pnpm dlx husky-init && pnpm install -w && pnpm install --save-dev -w ",
};
const preCommitPackages = "lint-staged";
const commitMsgPackages = "@commitlint/cli @commitlint/config-conventional commitizen cz-git";
// 需要安装的包
const packages = commitlint ? `${preCommitPackages} ${commitMsgPackages}` : preCommitPackages;
// 需要安装的包的安装命令
const command = `${huskyCommandMap[manager]}${packages}`;
const createCommitHook = `npx husky set .husky/pre-commit "npm run lint:lint-staged"`;
const createMsgHook = `npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'`;
// 需要创建钩子的命令
const createHookCommand = commitlint ? `${createCommitHook} && ${createMsgHook}` : createCommitHook;

lint-staged 配置

一般的lint-staged.config.js长这样:

module.exports = {
	"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
	"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": ["prettier --write--parser json"],
	"package.json": ["prettier --write"],
	"*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"],
	"*.{scss,less,styl,html}": ["stylelint --fix", "prettier --write"],
	"*.md": ["prettier --write"],
};

所以呢,需要根据项目使用的lint来生成lint-staged.config.js:

// 简单粗暴的函数
export function getLintStagedOption(lint) {
	const jsOp = [],
		jsonOp = [],
		pakOp = [],
		vueOp = [],
		styleOp = [],
		mdOp = [];
	if (lint.includes("eslint")) {
		jsOp.push("eslint --fix");
		vueOp.push("eslint --fix");
	}
	if (lint.includes("prettier")) {
		jsOp.push("prettier --write");
		vueOp.push("prettier --write");
		mdOp.push("prettier --write");
		jsonOp.push("prettier --write--parser json");
		pakOp.push("prettier --write");
		styleOp.push("prettier --write");
	}
	if (lint.includes("stylelint")) {
		vueOp.push("stylelint --fix");
		styleOp.push("stylelint --fix");
	}
	return {
		"*.{js,jsx,ts,tsx}": jsOp,
		"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": jsonOp,
		"package.json": pakOp,
		"*.vue": vueOp,
		"*.{scss,less,styl,html}": styleOp,
		"*.md": mdOp,
	};
}
// lint-staged.config.js 内容
const lintStagedContent = `module.exports =${JSON.stringify(getLintStagedOption(selectLint || pakHasLint))}`;
// lint-staged.config.js 文件
const lintStagedFile = resolve(projectDirectory, "lint-staged.config.js");

commitlint 配置

因为commitlint.config.js中的配置过于复杂。所以,我选择在安装完依赖后直接copy文件!被copy的文件内容:

// @see: https://cz-git.qbenben.com/zh/guide
/** @type {import('cz-git').UserConfig} */
module.exports = {
	ignores: [(commit) => commit.includes("init")],
	extends: ["@commitlint/config-conventional"],
	// parserPreset: "conventional-changelog-conventionalcommits",
	rules: {
		// @see: https://commitlint.js.org/#/reference-rules
		"body-leading-blank": [2, "always"],
		"footer-leading-blank": [1, "always"],
		"header-max-length": [2, "always", 108],
		"subject-empty": [2, "never"],
		"type-empty": [2, "never"],
		"subject-case": [0],
		"type-enum": [2, "always", ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"]],
	},
	prompt: {
		alias: { fd: "docs: fix typos" },
		messages: {
			type: "选择你要提交的类型 :",
			scope: "选择一个提交范围(可选):",
			customScope: "请输入自定义的提交范围 :",
			subject: "填写简短精炼的变更描述 :\n",
			body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
			breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
			footerPrefixsSelect: "选择关联issue前缀(可选):",
			customFooterPrefixs: "输入自定义issue前缀 :",
			footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
			confirmCommit: "是否提交或修改commit ?",
		},
		types: [
			{ value: "feat", name: "feat:     
      

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们