Консистентность среды
Цель
Поведение проекта должно быть идентичным во всех средах:
- Локальная разработка
- CI
- Staging
- Production
Расхождение между локальной средой и CI – дефект конфигурации проекта.
Общие принципы
MUST
- Среда выполнения приложения должна быть детерминированной
- Код, конфигурация и зависимости должны вести себя одинаково во всех окружениях
- CI pipeline обеспечивает воспроизводимую проверку сборки и тестов
- При расхождениях между локальным запуском и CI следует ориентироваться на результаты CI
Фиксация runtime и зависимостей
Версии инструментов
MUST
В корне проекта обязательно фиксируются:
- Версия Node.js
- Версия менеджера пакетов
- Версии глобальных CLI-инструментов (если используются)
Допустимые способы фиксации
Рекомендуемый:
- mise – основной инструмент
Поддерживается:
- asdf – на существующих проектах
- package.json → engines
- package.json → packageManager
✅ Примеры фиксации
Через mise (.mise.toml):
[tools]
node = "20.11.1"
pnpm = "9.12.0"
Через asdf (.tool-versions):
nodejs 20.11.1
pnpm 9.12.0
Через package.json:
{
"engines": {
"node": ">=20.11.0 <21"
},
"packageManager": "[email protected]"
}
Версии зависимостей
MUST
- Lock-файлы (pnpm-lock.yaml, yarn.lock, package-lock.json) должны быть в репозитории
- Любое изменение зависимостей – только через менеджер пакетов
FORBIDDEN
- Использовать альтернативный менеджер пакетов локально
- Расхождение версий package manager между разработчиками
- Выполнять установку зависимостей через глобально установленные инструменты (например, npx без локального контекста)
❌ Примеры плохих практик
Проблема: Разные версии Node.js
- Локально: Node 18
- CI: Node 20
- Результат: разные версии зависимостей, полифиллы, ESM поведение, build работает у одного и падает у другого
Проблема: Несколько package managers
- Один разработчик использует pnpm
- Другой использует yarn
- Результат: в репозитории два lockfile, разные версии пакетов
Консистентность package scripts
MUST
- scripts в package.json – единственный источник команд
- Все команды, используемые в CI, обязаны выполняться локально через scripts
- Порядок выполнения команд должен быть одинаковым локально и в CI
- Скрипты должны быть кроссплатформенными (Windows/macOS/Linux)
FORBIDDEN
- Запуск сборки напрямую (vite build, next build) вне scripts
- Отсутствие команды в scripts, но присутствие её в CI – дефект проекта
- Использование локальных shell-скриптов, отсутствующих в репозитории
- Использование .bashrc/.zshrc или alias как части поведения проекта
✅ Примеры корректных scripts
{
"scripts": {
"dev": "env-cmd -f .env.local next dev",
"build": "env-cmd -f .env.production next build",
"lint": "env-cmd -f .env.local eslint .",
"typecheck": "env-cmd -f .env.local tsc --noEmit",
"test": "env-cmd -f .env.test vitest run",
"test:watch": "env-cmd -f .env.test vitest watch"
}
}
CI использует только scripts:
steps:
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm test
- run: pnpm build
❌ Плохие практики
CI вызывает команды напрямую:
steps:
- run: next build # обход scripts
- run: eslint . # обход scripts
- run: tsc --noEmit # обход scripts
Управление переменными окружения
MUST
Все переменные окружения обязаны быть документированы:
- В .env.example – структура переменных
- В README.md – описание назначения
- В отдельном файле (например, docs/env_variables.md) – если:
- Проект содержит более 20 переменных окружения
- Необходима дополнительная документация: допустимые значения, зависимости между переменными, детальные описания
Переменные окружения допускаются для
- URL и endpoints сервисов
- Параметров инфраструктуры
- Определения окружения (development, staging, production, test)
- Конфигурации аналитики, логирования и трейсинга
FORBIDDEN
- Использование недокументированных переменных
- Использование .env.local и подобных файлов как механизма изменения логики проекта
✅ Пример: .env.example
# Application
APP_ENV=development
NODE_ENV=development
# API Configuration
API_BASE_URL=https://api.example.com
API_TIMEOUT=30000
# Infrastructure
ASSETS_HOST=https://cdn.example.com
SENTRY_DSN=https://[email protected]/123456
# Feature Configuration
LOG_LEVEL=info
ENABLE_TRACING=false
❌ Плохая практика (env управляет бизнес-логикой)
// ❌ ЗАПРЕЩЕНО
if (process.env.ENABLE_NEW_CHECKOUT === 'true') {
showNewCheckout();
}
// ❌ ЗАПРЕЩЕНО
const features = {
checkout: process.env.FEATURE_CHECKOUT === 'true',
payment: process.env.FEATURE_PAYMENT === 'true'
};
Infrastructure Configuration Branches
MUST
- Infrastructure branches допускаются только в
app/иshared/. Features/entities/widgets/pages/не должны содержать инфраструктурных различий.- Различия среды реализуются через:
- конфигурационные модули
- dependency injection
- environment-specific adapters
Разрешено для
- API endpoints, asset hosts
- Аналитика, error tracking, tracing
- Различия runtime-платформ (edge/node)
- Настройка инфраструктурных ключей и токенов
FORBIDDEN
- Любые инфраструктурные различия в feature/доменных слоях
Диаграмма
Feature Flags
MUST
- Feature Flags – отдельный механизм управления функциональностью
- Единая точка объявления и доступа
- Не должны создавать неконтролируемые различия между окружениями
Применение
Поэтапный rollout, A/B-тестирование, kill-switch, временное вкл/выкл функционала.
Варианты реализации
1. Hardcoded флаги – временные переключатели на этапе разработки
2. Env-переменные – различное поведение между средами (dev/stage/prod)
3. Feature flag системы – runtime управление без релиза, поэтапное включение, A/B тестирование
✅ Пример
// shared/feature-flags/types.ts
export type FeatureFlagName = 'newCheckout' | 'newOnboarding';
// shared/feature-flags/provider.ts
export const featureFlags = {
isEnabled: (name: FeatureFlagName) => getFlagValue(name),
};
// features/checkout/ui/CheckoutPage.tsx
const useNew = featureFlags.isEnabled('newCheckout');
Ограничения .env-файлов
FORBIDDEN
.env.local, .env.development запрещено использовать для:
- Отключения линтеров, форматтеров, тестов
- Обхода git hooks
- Создания условий с проверками только у отдельных разработчиков
.env.production, .env.staging не могут содержать клиентские переменные:
- Google Ads, GTM, Analytics ключи
Правило: Клиентские переменные встраиваются на этапе сборки через NEXT_PUBLIC_*.
❌ Примеры нарушений
# .env.local - ЗАПРЕЩЕНО
SKIP_LINT=true
SKIP_TESTS=true
# .env.production - ЗАПРЕЩЕНО
GOOGLE_ADS_KEY=pub-1234567890
GTM_ID=GTM-XXXXXX
CI как источник правды
MUST
- CI pipeline – эталонная среда выполнения проекта
- Если код работает локально, но падает в CI – это ошибка локальной конфигурации, а не CI
- Исправление расхождений – наивысший приоритет
Типовые примеры расхождений
- Расхождение версий Node.js
- Расхождение зависимостей или lock-файла
- Ошибки сборки из-за платформенных различий (ARM/x86)
- О тличия окружения или флагов сборки
Диаграмма: CI как источник правды
Допустимые исключения
MUST
Исключения допускаются только если:
- Есть техническое обоснование
- Получено согласование с Tech Lead
- Исключение задокументировано в README или docs
Незадокументированные исключения считаются нарушением требований регламента.
Формат документации исключения
Документация должна содержать:
- Что именно исключено – конкретное правило или требование
- Почему – техническое обоснование
- На какой срок – временное или постоянное исключение
- Как проверить/воспроизвести – инструкция для других разработчиков
Использование Docker (опционально)
Docker может потребоваться, если
- Проект содержит native-зависимости или бинарники, критичные к архитектуре
- В репозитории есть backend (монорепо / монолит)
- Есть e2e/интеграционные тесты, чувствительные к окружению
- В команде смешанные архитектуры (Intel и ARM)
Docker допускается, но не обязателен, если
- Проект – SPA или frontend-only
- Весь runtime контролируется Node.js
- Версии Node и package manager зафиксированы
- Нет локальных инфраструктурных сервисов (DB/Redis)
MUST (при использовании Docker)
- Docker не является "второй средой"
- Docker-контейнер обязан повторять CI/runtime окружение
- Dockerfile не должен отличаться от CI-образа
❌ Плохая практика
Dockerfile отличается от CI:
- CI использует Node 20 + pnpm
- Dockerfile использует Node 18 + npm
Результат: Это считается дефектом конфигурации.