Как использовать Git Hooks в проектах JavaScript
Git hooks автоматически запускают пользовательские сценарии, когда в репозитории Git происходит определенное событие. Есть две группы hooks: клиентские и серверные.
Вы можете ознакомиться с полной документацией для Git hooks, но в данной статье расскажу только о pre-commit
и pre-push
. Вы сможете написать свой собственный скрип с нуля, либо проверить готовые инструменты в репозитории GitHub.
Проблема(ы)
Можете ли вы представить, что над проектом работает много разработчиков, и у каждого свой стиль кода? Да, я знаю, что есть инструменты под названием ESLint, Prettier и Stylelint, но как убедиться, что все установили его в свою IDE или запускают команду в ручную?
Возможно, в ваш проект был интегрирован Sentry, и вам необходимо обновить выпуск с основной версии на младшую. Люди совершают ошибки: мы не можем гарантировать, что никогда не забудем обновить релиз Sentry перед отправкой коммитов.
Решения
Поскольку нам нужны hooks как pre-commit
, так и pre-push
, наш выбор оставили Husky and Lefthook. Husky получил более простую конфигурацию по сравнению Lefthook. Затем нам понадобится помощь от lint-staged, чтобы заставить всех следовать стандарту, прежде чем отправлять свой код в репозиторий; найдите строку выпуска Sentry и замените ее правильной версией в pre-push
.
Приступаем к работе
Установка:
npm i -D husky lint-staged
// or
pnpm i -D husky lint-staged
// or
yarn add husky lint-staged -D
Включаем Git hooks:
npx husky install
Убеждаемся, что у каждого разработчика в проекте включены свои Git hooks. Сделайте это в своем package.json
, который автоматически включит husky
всякий раз, когда вы запускаете npm install
локально.
// package.json
{
"scripts": {
"prepare": "husky install"
}
}
Pre-commit
Добавьте нижеприведенный фрагмент в package.json
:
// package.json
{
"lint-staged": {
"*.{js,vue}": "eslint --cache --fix"
}
}
Приведенный выше фрагмент можно понять следующим образом: всякий раз, когда файл с расширением js
или vue
был подготовлен, он запускает команду eslint --cache --fix
. Это позволит попытаться исправить проблему ESLint, когда это возможно. В противном случае он вернет ошибку(и), и вам придется исправить ее, прежде чем совершать ее снова.
Создайте файл по адресу .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
Когда вы запускаете git commit
, команда npx lint-staged
будет выполнятся перед успешной фиксацией.
Pre-push
Нам нужен собственный скрипт для обновления версии Sentry перед отправкой.
const fs = require("fs");
const path = require("path");
const readline = require("readline");
const git = require("isomorphic-git");
const dayjs = require("dayjs");
const DIR = path.resolve(__dirname, "../");
const TARGET_FILE = "vite.config.js"; // TODO: REPLACE
const getReleaseName = async () => {
const branch = await git.currentBranch({
fs,
dir: DIR,
});
let release = "";
// Only update the release when it's a release or hotfix branch
if (branch.startsWith("release/")) {
release = `release@${dayjs().format("YYYY.MM.DD")}`; // TODO: REPLACE
} else if (branch.startsWith("hotfix/")) {
release = `${branch.replace("/", "@")}@${dayjs().format("YYYY.MM.DD")}`; // TODO: REPLACE
}
return release;
};
(async () => {
const target = path.resolve(DIR, TARGET_FILE);
const fileStream = fs.createReadStream(target);
let tempStr = "";
const release = await getReleaseName();
if (release === "") return;
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});
for await (const line of rl) {
if (line.includes('release: "')) {
const matches = line.match(/"(.*?)"/);
const currentRelease = matches ? matches[1] : line;
if (currentRelease === release) return;
tempStr += `\n${line.replace(/"([^"]+)"/g, `"${release}"`)}`;
} else {
tempStr += `\n${line}`;
}
}
fs.writeFileSync(target, tempStr.trimStart() + "\n", {
flag: "w",
});
const status = await git.status({ fs, dir: DIR, filepath: TARGET_FILE });
if (status === "*modified") {
// stage and commit the updated file
await git.add({ fs, dir: DIR, filepath: TARGET_FILE });
await git.commit({
fs,
dir: DIR,
author: {
name: "Oyster Lee", // TODO: REPLACE
email: "oysterd3@gmail.com", // TODO: REPLACE
timezoneOffset: 8, // TODO: REPLACE
},
message: `🔖 chore(automated): update sentry release`,
});
}
})();
Релиз Sentry находится в vite.config.js
, таким образом, скрипт найдет строку, в которой ключевое слово release
: и заменит ее правильным именем релиза.
Добавьте приведенный ниже фрагмент в package.json
// package.json
{
"scripts": {
"prepush": "node scripts/update-sentry-release.js"
}
}
И создайте файл .husky/pre-push
с помощью приведенного ниже фрагмента
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run prepush
Известные проблемы
lint-staged
выдаст ошибку при попытке зафиксировать много промежуточных файлов в Windows. Проверьте открытую проблему.
Пользовательский скрипт update-sentry-release
работает только при отсутствии разрыва строки.
Когда кто-то обходит Git hooks и вы объединяете их коммиты, вам придется исправлять их ошибки linter.