Перейти к основному содержимому

Консистентность среды

Цель

Поведение проекта должно быть идентичным во всех средах:

  • Локальная разработка
  • 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

Все переменные окружения обязаны быть документированы:

  1. В .env.example – структура переменных
  2. В README.md – описание назначения
  3. В отдельном файле (например, 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

Исключения допускаются только если:

  1. Есть техническое обоснование
  2. Получено согласование с Tech Lead
  3. Исключение задокументировано в 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

Результат: Это считается дефектом конфигурации.