Проблема: код ломается после коммита
Сколько раз это случалось с вами: пушите код в репозиторий, CI падает с ошибкой линтера или тестов, которую вы могли поймать локально за 5 секунд. Коллеги ждут, пайплайн занят, а вы делаете ещё один коммит с фиксом.
Или классика: кто-то в команде коммитит код с console.log(), неиспользуемыми импортами или с форматированием "как получилось". Code review превращается в обсуждение отступов вместо архитектуры.
Цена проблемы:
- 10-15 минут на каждый сломанный CI-пайплайн
- Загрязнённая история коммитов
- Потеря времени ревьюеров на обсуждение форматирования
- Риск попадания сломанного кода в production
Решение: автоматизировать проверки до коммита с помощью git-хуков и Husky.
Git-хуки — это скрипты, которые автоматически запускаются на определённых этапах работы с Git (перед коммитом, перед пушем, после чекаута и т.д.). Husky — это инструмент, который упрощает настройку и управление этими хуками в современных JavaScript-проектах.
Что такое Husky и зачем он нужен
Husky — это популярный npm-пакет для управления git-хуками в JavaScript-проектах. Он позволяет:
- Запускать проверки автоматически перед коммитом или пушем
- Форматировать код перед коммитом (через Prettier или ESLint --fix)
- Запускать тесты перед пушем в удалённый репозиторий
- Валидировать commit-сообщения (через commitlint)
- Гарантировать качество кода на уровне локальной машины разработчика
Ключевое преимущество: проблемы находятся до того, как попадут в репозиторий, а не после.
С чем работает Husky
Husky отлично интегрируется с популярными инструментами:
Типичная связка:
- Husky — управляет git-хуками
- lint-staged — запускает проверки только на изменённых файлах (а не на всём проекте)
- ESLint — проверяет качество кода
- Prettier — форматирует код
- commitlint — проверяет формат commit-сообщений
Установка и базовая настройка
Шаг 1: Установка Husky
# Установка Husky (используйте версию 9+)
npm install --save-dev husky
# Инициализация Husky (создаёт .husky/ директорию)
npx husky initПосле выполнения команды npx husky init у вас появится:
- Директория
.husky/с примером хукаpre-commit - Скрипт
"prepare": "husky"вpackage.json
Что делает prepare-скрипт:
Этот скрипт автоматически запускается при npm install и настраивает Husky для каждого разработчика, который клонирует репозиторий. Это гарантирует, что git-хуки будут работать у всех в команде без дополнительных действий.
Шаг 2: Установка lint-staged
lint-staged — это инструмент, который запускает проверки только на файлах, которые вы добавили в staging area. Это критически важно для производительности: вместо проверки 1000 файлов вы проверяете только 5 изменённых.
npm install --save-dev lint-stagedСоздайте конфигурационный файл lint-staged.config.js в корне проекта:
module.exports = {
// Для JavaScript/TypeScript файлов
"*.{js,jsx,ts,tsx}": [
"eslint --fix", // Автофикс проблем ESLint
"prettier --write", // Форматирование через Prettier
],
// Для стилей
"*.{css,scss,less}": ["prettier --write"],
// Для JSON, Markdown и других файлов
"*.{json,md,mdx,yml,yaml}": ["prettier --write"],
};Важно: используйте lint-staged.config.js вместо встроенной конфигурации
в package.json, если у вас сложная логика или нужны комментарии.
Шаг 3: Настройка pre-commit хука
Теперь настроим хук, который будет запускаться перед каждым коммитом.
Откройте файл .husky/pre-commit (он был создан при инициализации) и замените его содержимое:
# .husky/pre-commit
npx lint-stagedВот и всё! Теперь перед каждым коммитом будет запускаться lint-staged, который проверит только изменённые файлы.
Шаг 4: Тестирование
Проверим, что хуки работают:
# Создайте файл с ошибкой ESLint
echo "const unused = 'variable'" > test.js
# Добавьте в staging
git add test.js
# Попробуйте закоммитить
git commit -m "test commit"Если всё настроено правильно, вы увидите:
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
Если ESLint нашёл ошибки, коммит будет заблокирован:
✖ eslint --fix:
error 'unused' is assigned a value but never used no-unused-vars
✖ lint-staged failed
Продвинутая настройка: production-ready конфигурация
Теперь настроим Husky для реального проекта с TypeScript, тестами и валидацией коммитов.
Конфигурация lint-staged с TypeScript
Для TypeScript-проектов важно не только проверять линтером, но и запускать type-checking:
// lint-staged.config.js
module.exports = {
// TypeScript/JavaScript файлы
"*.{ts,tsx,js,jsx}": [
"eslint --fix --max-warnings=0", // Блокируем коммит, если есть warnings
"prettier --write",
],
// Проверка типов TypeScript (только один раз на все файлы)
"*.{ts,tsx}": () => "tsc --noEmit",
// Стили
"*.{css,scss,module.css}": ["prettier --write"],
// Markdown и документация
"*.{md,mdx}": ["prettier --write"],
// Конфиги
"*.{json,yml,yaml}": ["prettier --write"],
};Обратите внимание: для TypeScript type-checking используется синтаксис () => "команда", потому что tsc проверяет весь проект целиком, а не отдельные файлы. Это важно для корректной работы системы типов.
Добавление pre-push хука для тестов
Запускать все тесты перед каждым коммитом — слишком медленно. Лучше запускать их перед пушем:
# Создаём pre-push хук
npx husky add .husky/pre-push "npm test"Или если вы используете конкретный тестовый раннер:
# .husky/pre-push
npm run test:ci # Запускает тесты без watch-режимаВалидация commit-сообщений с commitlint
Установим commitlint для проверки формата коммитов (например, Conventional Commits):
# Установка commitlint
npm install --save-dev @commitlint/cli @commitlint/config-conventional
# Создание конфига
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
# Добавление хука
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'Теперь коммиты должны соответствовать формату:
feat: добавлена новая фича
fix: исправлен баг в модуле авторизации
docs: обновлена документация
chore: обновлены зависимости
Попытка закоммитить с неправильным форматом будет заблокирована:
git commit -m "добавил фичу"
# ⧗ input: добавил фичу
# ✖ subject may not be empty [subject-empty]
# ✖ type may not be empty [type-empty]Реальный кейс: настройка для Next.js проекта
Вот полная конфигурация из production-проекта на Next.js 15 с TypeScript:
// lint-staged.config.js
module.exports = {
// TypeScript и JavaScript
"*.{ts,tsx,js,jsx}": ["eslint --fix --max-warnings=0", "prettier --write"],
// Type-checking для TypeScript (на весь проект)
"*.{ts,tsx}": () => "tsc --noEmit",
// Стили и CSS Modules
"*.{css,scss}": ["prettier --write"],
// MDX контент (блог, документация)
"*.{md,mdx}": ["prettier --write"],
// JSON конфиги
"*.json": ["prettier --write"],
};# .husky/pre-commit
npx lint-staged# .husky/pre-push
npm run build # Проверяем, что билд не сломан
npm run test:ci # Запускаем тесты// commitlint.config.js
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
[
"feat", // Новая фича
"fix", // Багфикс
"docs", // Документация
"style", // Форматирование (не влияет на код)
"refactor", // Рефакторинг
"test", // Тесты
"chore", // Обновление зависимостей, конфигов
"perf", // Улучшение производительности
"ci", // CI/CD
"build", // Система сборки
"revert", // Откат коммита
],
],
"subject-case": [0], // Отключаем проверку регистра для русских коммитов
},
};Результат: весь код, который попадает в репозиторий, автоматически проходит проверки на качество, форматирование и типы. Вероятность сломанного CI — почти нулевая.
Husky для Python-проектов
Husky — это не только для JavaScript! Git-хуки работают с любым языком программирования. Давайте настроим Husky для Python-проекта с современными инструментами проверки качества кода.
Важно: Хотя Husky — это npm-пакет, он отлично работает с Python-проектами. Вам нужен только Node.js для установки самого Husky, но хуки будут запускать Python-инструменты.
Инструменты для Python
Современный стек для Python-проектов:
- Ruff — сверхбыстрый линтер и форматер (замена Flake8, isort, и частично Black)
- Black — непримиримый форматировщик кода
- mypy — проверка типов (type checking)
- pytest — тестирование
- isort — сортировка импортов (если не используете Ruff)
Базовая настройка для Python
Шаг 1: Установка Husky
# Инициализируем npm проект (если его ещё нет)
npm init -y
# Устанавливаем Husky
npm install --save-dev husky lint-staged
npx husky initШаг 2: Установка Python-инструментов
# Через pip
pip install ruff black mypy pytest
# Или через poetry
poetry add --group dev ruff black mypy pytest
# Или через requirements-dev.txt
echo "ruff>=0.1.0" >> requirements-dev.txt
echo "black>=23.0.0" >> requirements-dev.txt
echo "mypy>=1.7.0" >> requirements-dev.txt
echo "pytest>=7.4.0" >> requirements-dev.txt
pip install -r requirements-dev.txtШаг 3: Настройка lint-staged для Python
Создайте lint-staged.config.js:
module.exports = {
// Python файлы: проверка Ruff и форматирование Black
"*.py": [
"ruff check --fix", // Проверка и автофикс через Ruff
"black", // Форматирование через Black
"mypy --ignore-missing-imports", // Type checking
],
// Jupyter notebooks (если используете)
"*.ipynb": ["ruff check --fix"],
// YAML конфиги
"*.{yml,yaml}": [
"yamllint", // Линтер для YAML
],
// Markdown документация
"*.md": [],
};Шаг 4: Настройка pre-commit хука
# .husky/pre-commit
npx lint-stagedШаг 5: Настройка pre-push хука для тестов
# .husky/pre-push
pytest tests/ # Запуск всех тестовВариант 1: Только Ruff (самый быстрый)
Ruff — это современный "всё-в-одном" инструмент для Python. Он заменяет Flake8, isort, pyupgrade и частично Black.
// lint-staged.config.js
module.exports = {
"*.py": [
"ruff check --fix --select I", // Проверка и сортировка импортов
"ruff check --fix", // Проверка и автофикс всех правил
"ruff format", // Форматирование (альтернатива Black)
],
};Конфигурация Ruff (pyproject.toml):
[tool.ruff]
# Максимальная длина строки
line-length = 88
# Python версия
target-version = "py311"
# Файлы для игнорирования
exclude = [
".git",
".venv",
"__pycache__",
"build",
"dist",
]
[tool.ruff.lint]
# Правила для проверки (эквивалент Flake8, pycodestyle, isort и др.)
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"T10", # flake8-debugger
"SIM", # flake8-simplify
]
# Игнорируемые правила
ignore = [
"E501", # line-too-long (Black справляется)
]
# Автофикс для правил
fixable = ["ALL"]
unfixable = []
[tool.ruff.format]
# Использовать двойные кавычки
quote-style = "double"
# Отступы
indent-style = "space"
# Совместимость с Black
skip-magic-trailing-comma = false
line-ending = "auto"Преимущество Ruff: скорость! Ruff в 10-100 раз быстрее Flake8/Pylint и работает за миллисекунды даже на больших проектах.
Вариант 2: Ruff + Black + mypy (классический стек)
Если вы хотите использовать Black для форматирования и mypy для type-checking:
// lint-staged.config.js
module.exports = {
"*.py": [
"ruff check --fix --select I", // Сортировка импортов через Ruff
"black --check", // Проверка форматирования
"black", // Применение форматирования
"ruff check --fix", // Линтинг через Ruff
"mypy", // Type checking
],
};Конфигурация Black (pyproject.toml):
[tool.black]
line-length = 88
target-version = ['py311']
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.venv
| build
| dist
)/
'''Конфигурация mypy (pyproject.toml):
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
no_implicit_optional = true
# Игнорировать отсутствующие типы в библиотеках
ignore_missing_imports = true
# Строгий режим (опционально)
# strict = trueВариант 3: Pylint вместо Ruff (для строгих проверок)
Pylint — более строгий и детальный линтер, но медленнее Ruff.
// lint-staged.config.js
module.exports = {
"*.py": [
"black", // Форматирование
"isort", // Сортировка импортов
"pylint --errors-only", // Только ошибки (быстрее)
// "pylint", // Полная проверка (медленно)
"mypy", // Type checking
],
};Конфигурация Pylint (.pylintrc):
[MASTER]
# Игнорируемые файлы
ignore=CVS,.git,__pycache__,.venv
# Количество процессов (0 = автоопределение)
jobs=0
[MESSAGES CONTROL]
# Отключённые правила
disable=
C0111, # missing-docstring
C0103, # invalid-name
R0903, # too-few-public-methods
W0212, # protected-access
[FORMAT]
# Максимальная длина строки
max-line-length=88
# Отступы
indent-string=' '
[DESIGN]
# Максимум аргументов функции
max-args=7
# Максимум атрибутов класса
max-attributes=10Внимание: Pylint медленнее Ruff в 50-100 раз. На больших проектах это
может замедлить коммиты до 10-30 секунд. Рекомендуется использовать
--errors-only или переносить в pre-push.
Оптимизация: быстрые проверки в pre-commit, медленные в pre-push
Для больших Python-проектов рекомендуется разделить проверки:
Быстрые проверки (pre-commit):
// lint-staged.config.js
module.exports = {
"*.py": [
"ruff check --fix --select I", // Импорты
"ruff format", // Форматирование
"ruff check --fix", // Быстрые проверки
],
};Медленные проверки (pre-push):
# .husky/pre-push
#!/bin/sh
# Type checking на весь проект
echo "Running mypy type checking..."
mypy src/
# Полные тесты
echo "Running pytest..."
pytest tests/ -v
# Проверка покрытия тестами (опционально)
# pytest tests/ --cov=src --cov-report=term-missing --cov-fail-under=80Реальный кейс: FastAPI проект с Poetry
Полная конфигурация для production FastAPI-проекта:
# pyproject.toml
[tool.poetry]
name = "my-fastapi-app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.11"
fastapi = "^0.104.0"
uvicorn = "^0.24.0"
pydantic = "^2.5.0"
sqlalchemy = "^2.0.0"
[tool.poetry.group.dev.dependencies]
ruff = "^0.1.0"
black = "^23.11.0"
mypy = "^1.7.0"
pytest = "^7.4.0"
pytest-cov = "^4.1.0"
pytest-asyncio = "^0.21.0"
httpx = "^0.25.0"
# Конфигурация Ruff
[tool.ruff]
line-length = 88
target-version = "py311"
exclude = [".venv", "migrations"]
[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "UP", "B", "C4", "SIM"]
ignore = ["E501"]
fixable = ["ALL"]
# Конфигурация Black
[tool.black]
line-length = 88
target-version = ['py311']
exclude = '''/(\.venv|migrations)/'''
# Конфигурация mypy
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
plugins = ["pydantic.mypy"]
[[tool.mypy.overrides]]
module = "sqlalchemy.*"
ignore_missing_imports = true
# Конфигурация pytest
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_functions = "test_*"
asyncio_mode = "auto"// lint-staged.config.js
module.exports = {
"*.py": [
"ruff check --fix --select I",
"ruff format",
"ruff check --fix",
"mypy",
],
"*.{json,yml,yaml}": [],
};# .husky/pre-commit
npx lint-staged# .husky/pre-push
#!/bin/sh
echo "🔍 Running type checking..."
poetry run mypy src/
echo "🧪 Running tests..."
poetry run pytest tests/ -v --cov=src --cov-report=term-missing
echo "✅ All checks passed!"// package.json
{
"name": "my-fastapi-app",
"private": true,
"scripts": {
"prepare": "husky"
},
"devDependencies": {
"husky": "^9.0.0",
"lint-staged": "^15.0.0"
}
}Django проект: особенности настройки
Для Django-проектов добавьте проверку миграций:
// lint-staged.config.js
module.exports = {
"*.py": [
"ruff check --fix --select I",
"ruff format",
"ruff check --fix",
"mypy --ignore-missing-imports",
],
// Проверка миграций при изменении моделей
"**/models.py": () => "python manage.py makemigrations --check --dry-run",
};# .husky/pre-push
#!/bin/sh
echo "🔍 Checking migrations..."
python manage.py makemigrations --check --dry-run
echo "🧪 Running tests..."
python manage.py test
echo "🔐 Running security checks..."
python manage.py check --deploy
echo "✅ All checks passed!"Сравнение инструментов для Python
Рекомендации:
- Для новых проектов: Ruff + mypy (быстро, современно, достаточно строго)
- Для legacy-проектов: Постепенная миграция с Pylint на Ruff
- Для строгих корпоративных стандартов: Ruff + Pylint (только ошибки) + mypy
Чек-лист для Python-проектов
- Установлен Husky и lint-staged
- Установлены Python-инструменты (Ruff/Black/mypy)
- Создан
pyproject.tomlс конфигурацией - Настроен
lint-staged.config.jsдля Python-файлов - Настроен
.husky/pre-commitдля быстрых проверок - Настроен
.husky/pre-pushдля тестов - Добавлен
.gitignoreдля Python (.venv,__pycache__, и т.д.) - Протестировано: коммит с ошибкой блокируется
- Протестировано: коммит с валидным кодом проходит
Проблемы и решения
Проблема 1: Хуки не работают после клонирования
Симптом: коллега клонировал репозиторий, но git-хуки не запускаются.
Причина: не выполнился prepare скрипт при npm install.
Решение:
Убедитесь, что в package.json есть:
{
"scripts": {
"prepare": "husky"
}
}Попросите коллег выполнить после клонирования:
npm install # Это запустит prepare автоматическиПроблема 2: Хуки слишком медленные
Симптом: коммит занимает 30+ секунд.
Причина: проверки запускаются на всех файлах проекта, а не только на изменённых.
Решение:
- Используйте
lint-staged— он проверяет только staged-файлы - Не запускайте тесты в
pre-commit, перенесите их вpre-push - Используйте кеширование в ESLint:
// lint-staged.config.js
module.exports = {
"*.{ts,tsx,js,jsx}": [
"eslint --cache --fix", // Добавляем --cache
"prettier --write",
],
};Проблема 3: Нужно обойти хуки в экстренном случае
Симптом: нужно срочно закоммитить, но хуки блокируют.
Решение (используйте с осторожностью!):
# Пропустить pre-commit и commit-msg хуки
git commit --no-verify -m "emergency fix"
# Пропустить pre-push хук
git push --no-verifyНе злоупотребляйте --no-verify! Используйте только в экстренных случаях
(hotfix на production). В остальных случаях — исправьте ошибки, которые нашёл
линтер.
Проблема 4: ESLint ругается на файлы, которые не должны проверяться
Причина: lint-staged передаёт в ESLint файлы, которые нужно игнорировать.
Решение:
Создайте/обновите .eslintignore:
# .eslintignore
node_modules/
.next/
out/
dist/
build/
*.config.js
Или настройте lint-staged явно:
module.exports = {
"*.{ts,tsx,js,jsx}": (filenames) => {
const filteredFiles = filenames
.filter((file) => !file.includes("node_modules"))
.filter((file) => !file.includes(".next"));
return `eslint --fix ${filteredFiles.join(" ")}`;
},
};Внедрение в существующий проект (миграция)
Если у вас уже есть большой проект с существующим кодом, который не проходит проверки:
Стратегия 1: Постепенное внедрение
- Установите Husky и lint-staged
- Настройте только Prettier (автофикс не ломает код):
// lint-staged.config.js
module.exports = {
"*.{ts,tsx,js,jsx}": ["prettier --write"],
};- Через неделю добавьте ESLint с --fix:
module.exports = {
"*.{ts,tsx,js,jsx}": ["eslint --fix", "prettier --write"],
};- Через месяц добавьте type-checking и тесты
Стратегия 2: Проверка только новых файлов
Используйте --max-warnings для постепенного улучшения:
module.exports = {
"*.{ts,tsx,js,jsx}": [
"eslint --fix --max-warnings=10", // Позволяем 10 warnings, постепенно снижаем
"prettier --write",
],
};Каждый спринт снижайте лимит: 10 → 5 → 0.
Стратегия 3: Игнорирование legacy-кода
Добавьте legacy-файлы в .eslintignore:
# Legacy код, который рефакторим постепенно
src/legacy/
src/old-api/
Новый код проверяется строго, старый — постепенно рефакторится.
Скрипты для package.json
Полезные команды для работы с git-хуками:
{
"scripts": {
"prepare": "husky",
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,css,md,json}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,css,md,json}\"",
"type-check": "tsc --noEmit",
"test:ci": "vitest run",
"validate": "npm run lint && npm run type-check && npm run test:ci"
}
}Команда validate полезна для проверки проекта целиком (например, перед созданием PR):
npm run validateМетрики успеха внедрения
Через месяц после внедрения Husky вы должны увидеть:
Дополнительные метрики:
- Время code review сокращается на 30-40% (не нужно обсуждать форматирование)
- Качество кода растёт (автоматические проверки находят проблемы рано)
- Меньше стресса у команды (CI почти не падает)
Чек-лист для внедрения
Используйте этот чек-лист для внедрения Husky в ваш проект:
- Установлен Husky (
npm install --save-dev husky) - Выполнена инициализация (
npx husky init) - Установлен lint-staged (
npm install --save-dev lint-staged) - Создан
lint-staged.config.jsс проверками - Настроен
.husky/pre-commitдля запуска lint-staged - (Опционально) Настроен
.husky/pre-pushдля тестов/билда - (Опционально) Установлен commitlint для валидации коммитов
- Добавлен
prepareскрипт вpackage.json - Протестировано: коммит с ошибкой ESLint блокируется
- Протестировано: коммит с валидным кодом проходит
- Команда проинформирована о новых правилах
- Документация обновлена (README или CONTRIBUTING.md)
Вывод
Husky и git-хуки — это не просто "удобная автоматизация". Это смена культуры разработки в команде.
Что вы получаете:
- Код всегда проходит минимальные проверки качества до попадания в репозиторий
- CI/CD пайплайны перестают падать из-за тривиальных ошибок
- Code review фокусируется на архитектуре и логике, а не на форматировании
- Новые разработчики сразу получают автоматические проверки после
npm install
Время на внедрение:
- Базовая настройка: 15-30 минут
- Production-ready конфигурация: 1-2 часа
- Окупаемость: уже через неделю (экономия времени на исправлении CI)
Следующий шаг:
Откройте терминал прямо сейчас и выполните:
npm install --save-dev husky lint-staged
npx husky initСоздайте lint-staged.config.js из примеров выше — и ваш проект уже защищён от тривиальных ошибок.
Автоматизация качества — это не про контроль, а про то, чтобы разработчики фокусировались на важном, а рутинные проверки выполнялись сами.
Ресурсы для дальнейшего изучения:
Git-хуки и автоматизация: Официальная документация Husky, lint-staged на GitHub, Conventional Commits, commitlint
Python инструменты: Ruff — официальная документация, Black — the uncompromising formatter, mypy — статическая проверка типов, Pylint — документация, pytest — тестирование



