Git workflow для TypeScript: как перестать бояться merge conflict
Если вы когда-нибудь пытались смержить две ветки, где одна меняла интерфейс User, а другая — добавляла новый тип в тот же самый модуль, вы знаете, что такое экзистенциальный кризис разработчика. TypeScript даёт нам мощную систему типов, но без правильного Git workflow эта мощь превращается в головную боль.
В этой статье мы разберём оптимальный Git workflow для TypeScript-проектов — от настройки веток до автоматической проверки типов при каждом коммите. Никакой воды, только то, что реально работает в бою.
Почему TypeScript требует особого подхода к Git?
TypeScript — это не просто «JavaScript с типами». Это язык, где каждое изменение может затронуть десятки файлов через interface, type и дженерики. Одна неверная правка в базовом типе — и весь проект перестаёт компилироваться.
Поэтому стандартный git workflow для TypeScript должен включать:
- Обязательную проверку типов перед коммитом
- Линтер, который не даёт закоммитить неформатированный код
- Чёткую стратегию ветвления, чтобы merge conflict не ломал типы
- CI/CD, который перепроверяет всё на сервере
Давайте разберём каждый пункт по порядку.
1. Выбираем стратегию ветвления: Git Flow или Trunk-based?
Для TypeScript-проектов я рекомендую Trunk-based development с короткоживущими ветками. Почему? Потому что TypeScript-код — это, по сути, «сырой» код, который нужно быстро проверять и деплоить. Долгие ветки приводят к тому, что типы «разъезжаются».
Базовая структура веток
main — стабильная версия, всегда готова к деплою
develop — основная ветка разработки
feature/* — новые фичи (живут 1-2 дня)
fix/* — багфиксы
release/* — подготовка релиза (опционально)
Пример для TypeScript-проекта:
git checkout -b feature/add-user-validation
# работаем...
git commit -m "feat: add email validation for User type"
git push origin feature/add-user-validation
# создаём PR → merge в develop
Важно: никогда не делайте rebase на main, если в проекте есть общие типы. Используйте merge — он сохраняет историю и не ломает зависимости.
2. Git Hooks: защита от дурака (и от себя)
Самый частый сценарий: вы забыли запустить tsc, закоммитили, запушили, а CI упал. Чтобы этого избежать, настраиваем pre-commit hooks.
Установка Husky + lint-staged
Husky — это инструмент для управления Git hooks. lint-staged — запускает команды только на изменённых файлах.
npm install --save-dev husky lint-staged
npx husky init
Создаём файл .husky/pre-commit:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
В package.json добавляем:
{
"lint-staged": {
"*.ts": [
"eslint --fix",
"prettier --write",
"tsc --noEmit"
]
}
}
Теперь перед каждым коммитом:
- Запускается ESLint с автофиксом
- Форматируется код через Prettier
- Проверяется сборка TypeScript (
tsc --noEmit)
Если хоть один шаг упадёт — коммит не пройдёт. Жестоко, но эффективно.
3. Правильные commit message: conventional commits
TypeScript-проекты выигрывают от conventional commits, потому что:
- Автоматически генерируется changelog
- Легко понять, какие изменения ломают типы
- Можно триггерить CI только на определённые коммиты
Примеры:
feat: add UserRole enum and validation
fix: correct type inference in useAuth hook
refactor: extract UserService from UserController
chore: update TypeScript to 5.4
Обратите внимание: breaking change в TypeScript — это всегда изменение публичного API или типа. Если вы удалили поле из интерфейса — это BREAKING CHANGE.
git commit -m "feat!: remove deprecated email field from User interface"
4. Code Review в TypeScript: на что обращать внимание
Когда вы открываете Pull Request, проверяйте не только логику, но и:
- Типы: не появился ли
any? Не слишком ли сложный дженерик? - Импорты: нет ли циклических зависимостей?
- Совместимость: ломает ли этот PR существующие типы?
Совет: добавьте в CI проверку API Extractor, чтобы видеть, как изменился публичный API библиотеки.
Пример проверки на сломанные типы
В CI можно добавить шаг:
npm run typecheck
# или
npx tsc --noEmit --project tsconfig.json
Если проект большой — используйте TypeScript Project References и проверяйте только изменённые модули.
5. CI/CD для TypeScript: быстрая обратная связь
После мержа в develop или main, ваш CI должен делать:
npm ci— чистая установка зависимостейnpx tsc --noEmit— проверка типовnpx eslint .— линтингnpm test— юнит-тестыnpm run build— сборка
Пример для GitHub Actions:
name: TypeScript CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx tsc --noEmit
- run: npx eslint .
- run: npm test
- run: npm run build
Этот workflow отсекает 90% проблем ещё до того, как код попадёт в main.
6. Работа с merge conflict в TypeScript
Типичный конфликт в TypeScript — это когда две ветки добавляют поля в один интерфейс. Вот как это выглядит:
<<<