目录 关于husky pre-commit commit-msg 一键添加husky 先把用到的import拿出来溜溜 package验证 husky安装询问 使用prompts进行交互提示 生成命令 lint-staged 配置 commitlint 配置 准备就绪,开始安装!
目录
- 关于husky
- pre-commit
- commit-msg
- 一键添加husky
- 先把用到的import拿出来溜溜
- package验证
- husky安装询问
- 使用prompts进行交互提示
- 生成命令
- lint-staged 配置
- commitlint 配置
- 准备就绪,开始安装!
- 发包
- 结尾
关于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: