Внедрение SonarQube в UI Kit монорепозиторий на Turborepo

Курс поможет фронтенд-разработчику внедрить SonarQube в монорепозиторий на Turborepo для UI Kit: от выбора архитектуры и настройки анализаторов до интеграции с CI и правил качества. В результате вы получите воспроизводимую конфигурацию анализа кода, тестового покрытия и quality gates для пакетов монорепо.

1. Что и зачем проверяем: метрики SonarQube для фронтенда и UI Kit

Что и зачем проверяем: метрики SonarQube для фронтенда и UI Kit

UI Kit в монорепозитории быстро растёт: компонентов становится больше, логика усложняется, а качество кода начинает зависеть от дисциплины команды. SonarQube помогает сделать качество наблюдаемым и управляемым: вы заранее договариваетесь, какие сигналы считаете проблемой, и автоматически проверяете их на PR и в CI.

Эта статья — про смысл метрик SonarQube именно для фронтенда и UI Kit. В следующих статьях курса мы будем настраивать анализ в Turborepo и подключать его к CI, поэтому здесь важно понять, что именно вы хотите контролировать и зачем.

!Поток проверки качества: от PR до Quality Gate и мержа

Что такое метрики SonarQube и чем они отличаются от правил

SonarQube анализирует код и выдаёт два типа результата:

  • Найденные проблемы (issues): конкретные места в коде с описанием, почему это плохо, и часто с рекомендацией.
  • Метрики: агрегированные показатели по проекту или по части кода (например, покрытие тестами, дублирование, количество проблем определённого типа).
  • Правила задаются в профиле качества (quality profile): это набор включённых/выключенных проверок для языка (например, JavaScript/TypeScript). А метрики используются, чтобы принимать решение о качестве на уровне проекта или PR.

    Полезные ссылки на термины SonarQube:

  • Issues (проблемы) в SonarQube
  • Определения метрик (metric definitions)
  • JavaScript/TypeScript анализ в SonarQube
  • Почему UI Kit — особый случай

    UI Kit — это не просто “ещё одно приложение”. Обычно у него есть особенности:

  • Пакет переиспользуется во многих продуктах, поэтому баги и небезопасные паттерны масштабируются на всю компанию.
  • Много компонентной логики: состояния, события, доступность, условный рендеринг.
  • Много “похожего” кода: варианты кнопок, модалок, таблиц, токены/темы — риск дублирования и расхождения поведения.
  • Тесты часто распределены между unit-тестами, снапшотами, Storybook-тестами, визуальной регрессией — и метрика покрытия может начать “обманывать”, если не договориться, какие тесты считаем.
  • Отсюда цель: настроить SonarQube так, чтобы он защищал вас от реальных рисков UI Kit, а не создавал шум.

    Ключевая идея: Quality Gate и подход Clean as You Code

    SonarQube обычно используют не как “отчёт на 200 страниц”, а как автоматический стоп-кран для плохих изменений.

  • Quality Gate — набор условий, которые определяют, можно ли принимать код (например, “на новом коде не должно быть новых багов и уязвимостей, покрытие нового кода не ниже X%”).
  • Clean as You Code — подход, при котором вы в первую очередь следите за качеством нового/изменённого кода, а не пытаетесь одномоментно “вылечить” весь легаси.
  • Ссылки:

  • Quality Gates в SonarQube
  • Clean as You Code
  • Какие метрики SonarQube чаще всего важны для фронтенда

    Ниже — практичный набор метрик, которые обычно дают наибольшую пользу в UI Kit.

    Bugs (баги) и Reliability

    Bugs — проблемы, которые с высокой вероятностью приведут к неправильному поведению (ошибки в логике, неверные условия, странные сравнения, потенциальные runtime-ошибки).

    Зачем в UI Kit:

  • UI Kit должен быть “скучно надёжным”: условный рендеринг, обработчики событий, работа с undefined, переключение вариантов компонента.
  • Баг в базовом компоненте (Button, Input) размножается на десятки приложений.
  • Как использовать:

  • На новом коде обычно разумно стремиться к 0 новых bugs.
  • Vulnerabilities (уязвимости) и Security

    Vulnerabilities — проблемы безопасности с высокой уверенностью (например, небезопасные конструкции, которые реально приводят к уязвимости).

    Зачем в UI Kit:

  • UI Kit часто работает с пользовательским вводом (поля, редакторы, markdown/HTML), ссылками, target="_blank", вставкой строк в DOM.
  • Как использовать:

  • Часто цель — 0 новых vulnerabilities.
  • Security Hotspots (точки внимания по безопасности)

    Security Hotspots — не “точно уязвимость”, а место, которое нужно осознанно проверить (например, использование API или паттерна, где безопасность зависит от контекста и правильной настройки).

    Зачем в UI Kit:

  • Во фронтенде много “условно опасных” вещей: рендер HTML, работа с URL, регулярки, интеграции.
  • Как использовать:

  • Ввести процесс: hotspot’ы не игнорировать, а переводить в статус reviewed после проверки.
  • Ссылка:

  • Security Hotspots в SonarQube
  • Code Smells и Maintainability (поддерживаемость)

    Code Smells — проблемы, которые не обязательно ломают поведение сейчас, но повышают стоимость изменений: слишком сложная функция, мёртвый код, запутанная структура, подозрительные конструкции.

    Зачем в UI Kit:

  • Компоненты живут долго и часто меняются.
  • Важно удерживать читаемость и предсказуемость паттернов: одинаковые пропсы, единый стиль обработки событий, единая логика disabled/loading и т.д.
  • Как использовать:

  • Сфокусироваться на новом коде: не копить новые smells.
  • Отдельно отслеживать “горячие точки” в компонентах, которые постоянно правят.
  • Coverage (покрытие тестами)

    Coverage показывает, какая доля кода выполнена при запуске тестов (обычно по отчёту покрытия от инструмента вроде Istanbul/nyc, Jest, Vitest).

    Зачем в UI Kit:

  • UI Kit — библиотека. Без тестов легко сломать поведение на краевых случаях (фокус, клавиатура, контролируемые/неконтролируемые поля, доступность).
  • Важные нюансы для фронтенда:

  • Coverage измеряет факт выполнения строк/веток, но не гарантирует качество теста.
  • В UI Kit часто есть Storybook и визуальные тесты: они могут быть полезны, но не всегда учитываются как coverage.
  • Практика:

  • В Quality Gate часто добавляют порог coverage на новом коде, чтобы не “закрывать глаза” на новые компоненты без тестов.
  • Duplications (дублирование)

    Duplications показывает, сколько кода повторяется.

    Зачем в UI Kit:

  • Дублирование в компонентах ведёт к расхождению поведения: один вариант кнопки фиксит баг, второй — нет.
  • Дублирование токенов/утилит часто означает, что нужно вынести общий слой.
  • Практика:

  • Следить за ростом дублирования на новом коде.
  • Использовать сигнал как повод для рефакторинга: выделить общие утилиты, базовые компоненты, общие хелперы для aria/keyboard.
  • Complexity (сложность) и “трудночитаемые” участки

    SonarQube показывает метрики сложности (в частности, часто используют Cognitive Complexity на уровне функций).

    Зачем в UI Kit:

  • Сложные render-ветки и “комбайны” из if/else в React-компонентах тяжело поддерживать.
  • Сложность часто коррелирует с багами: чем больше ветвлений и исключений, тем выше шанс забыть сценарий.
  • Практика:

  • Договориться о допустимой сложности для функций рендера и обработчиков.
  • При превышении — разбивать на подфункции, выносить вычисления, упрощать ветвления.
  • Рейтинги SonarQube: как их читать без самообмана

    SonarQube часто агрегирует результаты в рейтинги (например, по поддерживаемости/надёжности/безопасности). Их удобно смотреть как “светофор”, но решения лучше принимать по первичным сигналам:

  • Сколько новых bugs/vulnerabilities появилось.
  • Что именно за issues и где они находятся.
  • Какой coverage именно у изменённых файлов.
  • Практичное правило для UI Kit:

  • Останавливаем PR: новые bugs и новые vulnerabilities.
  • Требуем решение или явное обоснование: новые hotspots, резкое падение coverage на новом коде.
  • Планируем улучшение: smells, дублирование, высокая сложность — особенно если это затрагивает базовые компоненты.
  • Как выбрать “что проверяем” именно в вашем монорепо

    Чтобы метрики не превратились в шум, зафиксируйте договорённости:

  • Граница анализа
  • - Что входит: пакеты UI Kit (например, packages/ui, packages/tokens, packages/icons). - Что не входит: dist, сгенерированные файлы, возможно, story-файлы, если они не часть продукта.
  • Фокус на новом коде
  • - На старте почти всегда полезнее включить строгие требования именно к New Code.
  • Минимальный “стоп-набор” Quality Gate
  • - 0 новых bugs. - 0 новых vulnerabilities. - порог coverage на новом коде. - отсутствие критичного дублирования на новом коде.
  • Процесс для hotspots
  • - Кто и как их “reviewed”, что считается достаточной проверкой.

    Что будет дальше в курсе

    Дальше мы перейдём от смысла к практике:

  • как разложить анализ по пакетам в Turborepo;
  • как собирать coverage из тестов;
  • как настроить sonar-project.properties/параметры сканера для монорепозитория;
  • как подключить Quality Gate к PR в CI.
  • На этом этапе ваша задача — понять, какие сигналы качества важны именно для вашего UI Kit и какие из них должны блокировать merge.

    2. Архитектура монорепо на Turborepo и стратегия анализа по пакетам

    Архитектура монорепо на Turborepo и стратегия анализа по пакетам

    В прошлой статье мы договорились, что именно SonarQube должен контролировать для UI Kit: bugs, vulnerabilities, hotspots, smells, coverage, duplications и сложность, и почему важно опираться на Clean as You Code и Quality Gate.

    Теперь переходим к ключевому практическому вопросу: что считать единицей анализа в Turborepo-монорепозитории и как разложить SonarQube так, чтобы метрики были честными, PR не превращались в шум, а CI работал быстро.

    !Сравнение двух стратегий: один проект SonarQube на весь монорепо и отдельные проекты по пакетам

    Как Turborepo обычно устроен в UI Kit монорепо

    Turborepo почти всегда работает в связке с workspace-менеджером (npm workspaces, pnpm, Yarn). Типичная структура:

  • apps/
  • - потребители UI Kit (документация, Storybook как приложение, playground)
  • packages/
  • - библиотеки: ui, tokens, icons, utils, иногда eslint-config, tsconfig
  • turbo.json
  • - пайплайны задач и кэширование
  • корневые конфиги
  • - package.json, tsconfig.base.json, .eslintrc, и прочее

    Полезно понимать две особенности Turborepo:

  • Turborepo оркестрирует задачи по пакетам и умеет запускать их выборочно по зависимостям и фильтрам.
  • Результаты задач кэшируются, поэтому важно, чтобы Sonar-анализ запускался только там, где это имеет смысл, и использовал корректные артефакты (например, lcov после тестов).
  • Ссылки:

  • Turborepo Documentation
  • Filtering в Turborepo
  • В SonarQube важно различать проект и папку

    В монорепо легко перепутать два понятия:

  • папка пакета в репозитории: packages/ui
  • проект SonarQube: сущность на сервере SonarQube со своим ключом, историей метрик, настройками, правами доступа и Quality Gate
  • Одна и та же папка может анализироваться:

  • как часть одного большого SonarQube-проекта
  • как отдельный SonarQube-проект (с собственной историей)
  • От этого выбора зависят:

  • насколько “чистыми” будут метрики и Issues
  • как будет считаться coverage
  • как будет выглядеть PR decoration (сколько проверок и где)
  • насколько удобно будет управлять Quality Gate и правами
  • Две базовые стратегии анализа монорепо

    Стратегия A: один SonarQube-проект на весь монорепо

    Идея: вы создаёте один проект в SonarQube и сканируете весь репозиторий одним запуском.

    Плюсы:

  • проще стартовать: один проект, один ключ, одна настройка
  • удобный “единый” отчёт по всему репозиторию
  • Минусы (часто критичны для UI Kit монорепо):

  • метрики смешиваются между пакетами (например, tokens без тестов может “портить” coverage всего проекта)
  • сложнее понять, какой пакет является источником проблем
  • сложнее выстраивать разные требования качества для разных пакетов
  • дольше работает анализ, особенно если затрагивает apps/
  • Когда подходит:

  • маленький монорепо, 1–2 пакета
  • один владелец, единый релизный цикл
  • цель на старте: “хотим хоть какой-то Quality Gate на PR”
  • Стратегия B: отдельный SonarQube-проект на ключевые пакеты

    Идея: packages/ui, packages/tokens, packages/icons анализируются как отдельные SonarQube-проекты, каждый со своим ключом и Quality Gate.

    Плюсы:

  • метрики и coverage считаются по смысловой единице (пакету)
  • проще применять разные Quality Gate (например, ui строже, icons мягче)
  • проще ownership: команда UI отвечает за ui, платформа за tokens
  • проще масштабировать: добавили пакет, добавили проект
  • Минусы:

  • нужно больше CI-обвязки: несколько запусков сканера
  • в PR может появиться несколько checks (по числу анализируемых проектов)
  • Когда подходит (наиболее типично для UI Kit):

  • UI Kit состоит из нескольких пакетов
  • пакеты живут долго и должны иметь независимую историю метрик
  • важно, чтобы покрытие и дублирование не размывались между пакетами
  • Практичный выбор для UI Kit: разделяйте UI Kit и приложения

    Для монорепозитория, где есть apps/ и packages/, обычно рационально:

  • анализировать только пакеты UI Kit как полноценные “продуктовые” библиотеки
  • не анализировать apps/ на старте, либо анализировать их отдельными проектами позже
  • Почему:

  • UI Kit должен иметь устойчивые метрики и Quality Gate
  • приложения часто содержат экспериментальный код, демо-страницы, Storybook-конфигурации, которые создают шум
  • смешивание apps/ и packages/ часто искажает coverage и duplications
  • Как определить границы анализа внутри пакета

    Для каждого пакета нужно договориться, что считается:

  • sources: что попадает в анализ как продуктовый код
  • tests: какие файлы считаются тестами
  • exclusions: что исключаем, чтобы не анализировать “мусор”
  • Практичные правила для UI Kit пакетов:

  • исключайте сборочные артефакты
  • - dist/, build/, coverage/, .turbo/
  • исключайте сгенерированные файлы
  • - то, что создаётся скриптами или генераторами (иконки, типы из API и т.д.)
  • аккуратно решите судьбу Storybook
  • - .stories. часто лучше исключить из метрик, если ваша цель именно качество библиотечного кода

    В SonarQube это обычно делается через свойства sonar.sources, sonar.tests, sonar.exclusions, sonar.test.inclusions, sonar.coverage.exclusions.

    Ссылка:

  • SonarQube: Analysis Parameters
  • Coverage в монорепо: две рабочие модели

    Модель 1: coverage отдельно по пакетам

    Каждый пакет генерирует свой lcov.info (например, packages/ui/coverage/lcov.info), и анализ SonarQube этого пакета читает только свой отчёт.

    Плюсы:

  • честная метрика покрытия по пакету
  • проще поддерживать
  • Минусы:

  • нужен запуск тестов по каждому пакету (хотя Turborepo это хорошо оптимизирует)
  • Модель 2: объединённый coverage для “одного большого анализа”

    Если вы выбрали стратегию “один проект на весь монорепо”, удобнее собрать единый lcov.

    Риск:

  • при неправильных путях в lcov SonarQube может “не сопоставить” покрытие с исходниками и coverage станет 0% или частичным
  • В UI Kit монорепо чаще проще и надёжнее модель 1: coverage по пакетам, потому что она совпадает с ownership и с реальным качеством библиотек.

    Ссылка:

  • SonarQube: JavaScript/TypeScript Test Coverage
  • Как Turborepo помогает запускать анализ “по делу”

    Пайплайн задач

    Обычно Sonar-анализ должен зависеть от того, что генерирует артефакты для анализа:

  • типизация/сборка (не всегда обязательна для SonarQube, но часто полезна для стабильности)
  • тесты с coverage
  • линт (иногда отдельно, но часто достаточно Sonar-правил)
  • Turborepo позволяет описать это в turbo.json, чтобы sonar не запускался раньше test.

    Запуск только затронутых пакетов

    В PR нет смысла анализировать пакеты, которые не менялись. Turborepo умеет фильтровать выполнение по git-диапазону.

    Результат:

  • быстрее CI
  • меньше шума
  • дешевле инфраструктура
  • Ссылка:

  • Filtering в Turborepo
  • Рекомендуемая стратегия для курса

    Чтобы метрики из прошлой статьи работали как управляемые сигналы качества именно для UI Kit, дальше по курсу будем опираться на такую схему:

  • Отдельный SonarQube-проект на пакет для ключевых библиотек UI Kit (packages/ui, packages/tokens, packages/icons и подобные).
  • Единый подход к границам анализа:
  • - анализируем исходники пакета - исключаем dist, coverage, сгенерированное - отдельно решаем, включаем ли stories и __mocks__
  • Coverage формируется на уровне пакета, и каждый анализ использует свой lcov.info.
  • В CI анализ запускается только для затронутых пакетов.
  • Так вы получаете:

  • честные Quality Gate на New Code для каждой библиотеки
  • понятное владение метриками
  • масштабируемость по мере роста монорепо
  • Частые ошибки при разбиении анализа по пакетам

  • Смешали sources и tests
  • - SonarQube начинает считать тестовые файлы как продуктовый код или наоборот, и метрики “плывут”.
  • Не исключили артефакты сборки
  • - в анализ попадают dist или сгенерированные файлы, растёт дублирование и появляются странные issues.
  • Coverage не матчится с путями
  • - lcov с абсолютными путями или путями, которые не совпадают с тем, что Sonar считает baseDir.
  • Один Quality Gate на всё
  • - пакет tokens (почти без логики) и пакет ui (сложная логика) начинают спорить за один и тот же порог.

    Что дальше

    В следующей статье перейдём от архитектуры к конкретной настройке:

  • как организовать конфиги SonarQube для монорепо
  • как запускать сканер для конкретного пакета
  • как корректно подцепить lcov и исключения
  • как подготовить это к CI и PR-проверкам
  • 3. Развёртывание SonarQube и создание проекта/проектов в монорепо

    Развёртывание SonarQube и создание проекта/проектов в монорепо

    В прошлых статьях мы определили, что хотим контролировать в UI Kit (bugs, vulnerabilities, coverage, duplications и подход Clean as You Code) и как удобнее мыслить монорепозиторием на Turborepo (чаще всего проект SonarQube на пакет).

    Теперь цель практическая: поднять SonarQube (сам сервер), зайти в интерфейс, настроить базовые вещи и создать проекты так, чтобы дальше было удобно подключать анализ из CI и получать честные Quality Gate на PR.

    !Общая картина: где живёт SonarQube и как результаты анализа приходят в PR

    Что именно мы разворачиваем

    SonarQube состоит из двух частей:

  • SonarQube Server: веб-приложение и API, где хранятся проекты, история метрик, профили качества и Quality Gates.
  • SonarScanner: инструмент, который запускается на вашей машине или в CI, читает код и отчёты (например, lcov.info) и отправляет результаты на Server.
  • В этой статье мы поднимаем Server и создаём проекты. Запуск сканера и конфигурацию анализа в Turborepo разберём дальше.

    Официальные источники, на которые полезно опираться:

  • SonarQube Documentation
  • Install SonarQube Server
  • SonarQube on Docker
  • Выбор варианта: локально, в компании, или как сервис

    SonarQube Server (self-hosted)

    Подходит, если:

  • вы хотите полный контроль над настройками и данными;
  • у вас есть корпоративная сеть и приватные репозитории;
  • вы готовы поддерживать сервер (обновления, бэкапы).
  • SonarCloud (облачный сервис)

    Если у вас нет возможности поднимать сервер, обычно проще начать с SonarCloud. Но в рамках курса фокус на внедрение в монорепо, поэтому дальше будем описывать self-hosted SonarQube Server.

  • SonarCloud Documentation
  • Быстрый старт: SonarQube через Docker Compose

    Ниже пример для локального запуска. Для команды это полезно как песочница, чтобы отладить проекты, права и Quality Gate до внедрения в CI.

    Минимальный docker-compose.yml

    Пояснения:

  • sonarqube:lts-community — LTS-ветка, на практике её часто выбирают для предсказуемости обновлений.
  • SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true — упрощение для локального окружения. Для продакшена лучше идти по официальным требованиям установки.
  • Запуск:

    Проверка:

  • откройте http://localhost:9000
  • выполните вход
  • Официальные детали по Docker-установке:

  • Installing SonarQube from Docker
  • Первый вход и базовая защита

    После первого запуска SonarQube предложит зайти под администратором.

    Рекомендуемая минимальная гигиена перед тем, как подключать CI:

  • Сменить пароль администратора.
  • Создать отдельного пользователя для CI (например, ci-sonar).
  • Выдавать права не «всем», а через группы.
  • Это особенно важно в монорепо: проекты по пакетам могут иметь разных владельцев, и права удобнее раздавать пакетно.

    Создаём проекты под монорепо: один на пакет

    Из прошлой статьи мы выбирали стратегию: отдельный SonarQube-проект на ключевой пакет (packages/ui, packages/tokens, packages/icons). Теперь оформим это в правила именования и создания.

    Почему проект = пакет работает лучше всего

  • Метрики не смешиваются между разными типами кода.
  • Coverage считается честно для библиотеки.
  • Ownership проще: кто отвечает за пакет, тот отвечает за его Quality Gate.
  • Соглашения по ключам и именам проектов

    Сразу договоритесь о двух полях:

  • Project key — уникальный ключ, который будет использоваться сканером.
  • Display name — человеко-читаемое имя в интерфейсе.
  • Практичный шаблон для монорепо:

    | Пакет | Project key (пример) | Display name (пример) | |---|---|---| | packages/ui | ui-kit_ui | UI Kit: UI | | packages/tokens | ui-kit_tokens | UI Kit: Tokens | | packages/icons | ui-kit_icons | UI Kit: Icons |

    Важно:

  • не используйте случайные ключи, иначе через полгода будет трудно поддерживать CI-конфиги;
  • держите один префикс для всего монорепо (ui-kit_), чтобы проекты группировались.
  • Создание проектов в интерфейсе

    В SonarQube обычно достаточно:

  • Открыть раздел создания проекта.
  • Выбрать ручное создание.
  • Ввести Project key и Display name.
  • Повторить для каждого пакета.
  • Вам не нужно «указывать папку пакета» на этом шаге. Привязка к packages/ui произойдёт позже, когда сканер будет запускаться с корректным sonar.projectBaseDir или когда вы будете запускать сканер из директории пакета.

    Настройка New Code как основа Clean as You Code

    Чтобы Quality Gate работал по подходу Clean as You Code, SonarQube должен понимать, что считать новым кодом.

    Чаще всего для UI Kit в монорепо подходят два варианта:

  • New Code = изменения относительно основной ветки (например, main).
  • New Code = код после определённой даты (удобно на старте, если легаси много и вы хотите «зафиксировать точку отсчёта»).
  • Практичная рекомендация:

  • Для стабильного репозитория: ориентироваться на основную ветку.
  • Для монорепо с большим легаси: поставить дату старта внедрения, затем перейти на основную ветку.
  • Документация:

  • Clean as You Code
  • Quality Gate: делаем «стоп-кран» для PR

    Мы обсуждали, что UI Kit чаще всего хочет блокировать merge при появлении критичных проблем на новом коде. Это и есть роль Quality Gate.

    Что важно сделать на уровне сервера уже сейчас:

  • Выбрать базовый Quality Gate как старт.
  • При необходимости создать отдельный Quality Gate для UI Kit (например, UI Kit Gate).
  • Назначить этот Gate на проекты пакетов.
  • Документация:

  • Quality Gates
  • Примечание: конкретные пороги (например, покрытие на новом коде) зависят от вашей договорённости из первой статьи. На старте часто важнее включить саму дисциплину, чем поставить идеальные цифры.

    Quality Profile для TypeScript/JavaScript

    Для фронтенда SonarQube использует профиль качества (набор правил) для JavaScript/TypeScript.

    Рекомендация для старта:

  • не пытайтесь сразу «подружить» все правила с вашим линтером;
  • начните с дефолтного профиля и постепенно снижайте шум: отключайте спорные правила или переводите их в более мягкие уровни.
  • Документация по анализу JS/TS:

  • JavaScript/TypeScript
  • Токены доступа для CI: как безопасно подключить сканер

    Когда вы дойдёте до запуска анализатора из CI, вам понадобится токен.

    Рекомендуемый подход:

  • Создать отдельного пользователя ci-sonar.
  • Выдать ему права:
  • - на публикацию анализа (аналогично праву Execute Analysis) в конкретных проектах пакетов.
  • Сгенерировать токен и сохранить в секретах CI.
  • Почему так:

  • токен не привязан к личной учётке разработчика;
  • можно быстро отозвать доступ;
  • проще аудит и управление доступами.
  • Права и владение: группы под пакеты

    В монорепо удобнее управлять правами через группы.

    Пример модели:

  • группа ui-kit-maintainers имеет доступ администрирования проектов пакетов UI Kit;
  • группа developers имеет доступ просмотра;
  • пользователь ci-sonar имеет минимальные права, необходимые для публикации анализа.
  • Это особенно полезно, если вы реально разносите проекты по пакетам и хотите, чтобы разные команды отвечали за разные части.

    Проверочный список перед переходом к настройке сканера

    Перед тем как писать sonar-project.properties и подключать Turborepo/CI, убедитесь, что серверная часть готова:

  • SonarQube доступен по стабильному URL (не только localhost).
  • Созданы проекты под ключевые пакеты UI Kit.
  • Для проектов выбран корректный Quality Gate.
  • Настроена стратегия New Code.
  • Создан CI-пользователь и токен сохранён как секрет.
  • Если это сделано, дальше вы сможете запускать анализ по затронутым пакетам и получать Quality Gate на PR без смешивания метрик между пакетами.

    Что дальше

    В следующей статье перейдём к запуску анализа в контексте Turborepo:

  • как организовать конфиги анализа для каждого пакета;
  • как корректно подцепить lcov.info от Jest/Vitest;
  • как исключать dist, сгенерированное и, при необходимости, .stories.;
  • как запускать анализ только для затронутых пакетов.
  • 4. Конфигурация sonar-project.properties и пути монорепо (sources, tests, exclusions)

    Конфигурация sonar-project.properties и пути монорепо (sources, tests, exclusions)

    В прошлой статье мы подняли SonarQube Server и создали проекты. Теперь настало время самого ответственного этапа — настройки сканера. Часто именно здесь разработчики спотыкаются, получая пустые отчёты или странные метрики.

    Чтобы разобраться в теме, позовём нашего коллегу Илеза. Илез — талантливый фронтенд-разработчик, который переносит UI Kit компании на Turborepo. Он уже запустил сканер, но результат его озадачил: покрытие кода 0%, а в списке проблем (issues) появились файлы из папки dist и node_modules.

    Давайте поможем Илезу настроить sonar-project.properties так, чтобы SonarQube видел только то, что нужно, и игнорировал мусор.

    !Карта папок пакета и соответствие sources/tests/exclusions

    Проблема Илеза: «Где мои файлы?»

    Илез создал файл конфигурации, написал там sonar.sources=src, запустил сканер из корня монорепозитория и получил ошибку: "File src not found".

    Почему так произошло? Всё дело в базовой директории.

    Базовая директория (Base Directory)

    У SonarScanner есть точка отсчёта. Все пути, которые вы пишете в конфиге (src, coverage/lcov.info), считаются относительно этой точки.

    В монорепозитории есть два пути:

  • Заходить в папку пакета (cd packages/ui) и запускать сканер оттуда. Тогда база — это папка пакета.
  • Запускать из корня, но явно указывать сканеру, где лежит пакет, через параметр sonar.projectBaseDir.
  • Для Turborepo и CI/CD второй вариант надежнее. Мы не бегаем по папкам, а командуем из центра.

    Совет Илезу: > Илез, когда ты запускаешь сканер из корня, он ищет папку src в корне репозитория. А она у тебя в packages/ui/src. Либо указывай полный путь, либо задай sonar.projectBaseDir.

    Настройка источников: Sources и Tests

    Илез исправил путь, сканер заработал. Но теперь новая беда: SonarQube ругается на дублирование кода в тестах и считает тестовые файлы как продуктовый код, снижая общий рейтинг надежности.

    Это классическая ошибка конфигурации sources и tests.

    sonar.sources vs sonar.tests

    SonarQube делит все файлы на два типа:

  • Sources (Исходники): Продуктовый код. По нему считаются баги, уязвимости, дубли и покрытие.
  • Tests (Тесты): Тестовый код. По нему считаются только метрики количества тестов. Баги в тестах обычно менее критичны.
  • В современном React-компоненте у Илеза структура такая:

    Если Илез напишет просто:

    ...то SonarQube посчитает Button.test.tsx исходным кодом.

    Как разделить код и тесты, если они лежат в одной папке?

    Нам нужно использовать Inclusions (включения). Мы говорим Сонару: «Смотри в папку src и как на код, и как на тесты, но различай их по маске файла».

    Правильная конфигурация для Илеза:

    Теперь SonarQube увидит Button.test.tsx, проверит его маску, поймёт, что это тест, и уберет его из расчета метрик продуктового кода.

    Уборка мусора: Exclusions

    Илез радостно сообщает: «Всё работает! Но у меня 5000 новых issues!». Оказывается, сканер проанализировал папку dist (сборку), папку storybook-static и даже залез в node_modules.

    Чтобы этого избежать, нужны Exclusions (исключения).

    sonar.exclusions

    Этот параметр полностью выкидывает файлы из анализа. Они как будто не существуют для SonarQube.

    Что Илез должен добавить в исключения для UI Kit:

    * /dist/ — скомпилированный код нам не нужен. * /node_modules/ — чужой код не анализируем. * /.turbo/ — кэш турборепо. * /coverage/ — отчеты о покрытии.

    sonar.coverage.exclusions

    Это более тонкая настройка. Файлы остаются в анализе (мы видим в них баги), но исключаются из расчета покрытия (Coverage).

    Пример со Storybook: Илез написал Button.stories.tsx. Это не тест и не совсем продуктовый код. Покрывать его тестами бессмысленно. Если мы оставим его как есть, он снизит общий процент покрытия (так как он не протестирован).

    Решение:

    Теперь Button.stories.tsx будет проверен на синтаксические ошибки, но не будет тянуть метрику Coverage вниз.

    Итоговый шаблон для пакета UI Kit

    Илез собрал всё воедино. Вот идеальный sonar-project.properties, который он положил в папку packages/ui/.

    Чек-лист: почему у Илеза может снова быть 0% покрытия?

    Даже с этим конфигом можно получить 0%. Вот частые ловушки, в которые попадал Илез (и вы тоже можете):

  • Неверный путь к LCOV.
  • В конфиге написано coverage/lcov.info. Если Илез запускает тесты, и папка coverage создается внутри packages/ui/, то всё ок. Но если sonar.projectBaseDir настроен неправильно, сканер будет искать отчет не там.

  • Абсолютные пути внутри LCOV.
  • Иногда генераторы отчетов (Jest) пишут в файл lcov.info абсолютные пути (/Users/ilez/projects/...), а SonarScanner ожидает относительные. Обычно современные сканеры умеют это «разруливать», но стоит проверить, совпадают ли пути файлов в отчете с реальной структурой.

  • Файлы исключены.
  • Если Илез случайно добавит src/*/ в sonar.exclusions, то анализировать будет нечего, и покрытие будет пустым.

    Резюме

    Мы помогли Илезу настроить конфигурацию для пакета в монорепозитории. Главные выводы:

    * Храните sonar-project.properties внутри каждого пакета (packages/ui, packages/utils). * Используйте sonar.projectBaseDir при запуске, чтобы сканер понимал, где он находится. * Чётко разделяйте sources и tests через inclusions, если они лежат в одной папке. * Не бойтесь использовать coverage.exclusions для Storybook и индексных файлов, чтобы метрики были честными.

    В следующей статье мы автоматизируем запуск этого конфига через Turborepo, чтобы Илезу не приходилось запускать сканер вручную.

    5. TypeScript/ESLint, импорты и правила: настройка качества для UI компонентов

    TypeScript/ESLint, импорты и правила: настройка качества для UI компонентов

    В предыдущих статьях мы настроили SonarQube Server, выбрали стратегию проект на пакет и разобрали sonar-project.properties, чтобы SonarQube честно видел sources/tests/exclusions и подхватывал coverage.

    Следующий практический слой качества в UI Kit — это правила, которые влияют на ежедневную разработку компонентов:

  • TypeScript как контракт (типы, публичный API, безопасность refactor’ов)
  • ESLint как быстрая обратная связь (импорты, границы пакетов, локальные паттерны)
  • SonarQube как единый quality gate в CI (надежность/безопасность/поддерживаемость на новом коде)
  • Эта статья про то, как настроить правила вокруг TypeScript и импортов так, чтобы SonarQube получал корректный контекст анализа, а команда — понятные и не конфликтующие сигналы качества.

    !Пайплайн качества пакета: ESLint и тесты готовят базу, SonarQube делает итоговый gate

    Роли инструментов и почему важно развести ответственность

    Если не договориться о ролях, получится либо двойная проверка одного и того же (шум), либо дыры (важное не проверяется нигде).

    TypeScript

    TypeScript отвечает за:

  • корректность типов и контрактов (props, события, generics)
  • безопасность рефакторинга (переименования, извлечения, изменение сигнатур)
  • качество публичного API пакета (что экспортируем и как)
  • Официальная документация:

  • TSConfig Reference
  • ESLint

    ESLint отвечает за:

  • быстрые проверки в редакторе и в pre-commit
  • правила на уровне команды (импорты, ограничения, архитектурные границы)
  • автофиксы (например, удаление неиспользуемых импортов)
  • Официальная документация:

  • ESLint Documentation
  • SonarQube

    SonarQube отвечает за:

  • единый отчёт и историю метрик
  • качество New Code через Quality Gate
  • более “архитектурные” сигналы: code smells, complexity, duplications, reliability/security
  • Документация:

  • JavaScript/TypeScript analysis
  • Quality Profiles
  • Типовая цель для UI Kit: стабильный API и контролируемые зависимости

    Для UI компонентов качество часто ломается не “сложной бизнес-логикой”, а нарушениями дисциплины:

  • глубокие импорты в чужие внутренности (deep import)
  • циклические зависимости между компонентами/утилитами
  • импорт из “запрещённых” пакетов (например, UI тянет app-слой)
  • расползание публичного API (экспортируем всё подряд)
  • скрытые any, неявные unknown, агрессивные ! (non-null assertion)
  • Эти проблемы лучше всего ловятся комбинацией TypeScript + ESLint, а SonarQube дальше превращает это в управляемый gate на PR.

    TypeScript: настройки, которые напрямую влияют на качество анализа

    SonarQube для JS/TS анализирует код статически, но качество результата сильно зависит от того, насколько правильно проект описан через tsconfig.json.

    Выбор tsconfig для пакета

    В монорепо чаще всего есть:

  • корневой tsconfig.base.json (общие опции)
  • packages/<name>/tsconfig.json (опции пакета, include/exclude, ссылки на базу)
  • Рекомендация: в каждом анализируемом пакете держите свой tsconfig.json и убедитесь, что он:

  • включает исходники пакета (src)
  • исключает артефакты (dist, coverage, сгенерированное)
  • Пример packages/ui/tsconfig.json:

    Почему это важно:

  • если include/exclude настроены плохо, в анализ “протечёт” мусор (сборка, генерация) и метрики исказятся
  • если tsconfig не соответствует реальному коду пакета, TypeScript-часть анализа будет менее точной, а ошибки могут выглядеть странно
  • Алиасы импортов и paths

    UI Kit часто использует алиасы:

  • @ui/*
  • @tokens/*
  • @shared/*
  • Чтобы импорты были понятны TypeScript и инструментам, в базе обычно задают baseUrl и paths. Важно, чтобы эти настройки были доступны пакетному tsconfig.json через extends.

    Пример фрагмента tsconfig.base.json:

    Практика для UI компонентов:

  • алиасы помогают избегать “хрупких” относительных импортов вроде ../../../utils
  • но алиасы могут скрыть неправильные зависимости, поэтому их нужно подкреплять ESLint-правилами границ (об этом ниже)
  • Строгость типов как договорённость

    Для UI Kit обычно полезно, чтобы строгие настройки были включены, иначе ошибки “просачиваются” через базовые компоненты и распространяются на всех потребителей.

    Часто полезные опции в compilerOptions:

  • strict
  • noUncheckedIndexedAccess
  • noImplicitOverride (если используете классы)
  • Важно: включать строгость лучше поэтапно, но для нового кода (подход Clean as You Code) можно держать планку выше.

    ESLint: импорты, границы и правила, которые реально защищают UI компоненты

    ESLint особенно полезен там, где:

  • нужно быстро получить фидбек до CI
  • нужно enforce’ить архитектурные правила, специфичные для вашей команды
  • Базовый набор плагинов вокруг импортов

  • eslint-plugin-import
  • eslint-plugin-unused-imports
  • Что они дают для UI Kit:

  • контроль циклов и “неправильных” импортов
  • автоудаление неиспользуемых импортов
  • единообразие импортов, что снижает диффы и шанс конфликтов
  • Правила, которые обычно дают максимальный эффект в UI Kit

    Ниже примерный набор целей (не “копипаст”, а ориентир, что именно вы хотите запретить/зафиксировать).

    #### Запрет deep imports в чужие внутренности

    Проблема:

  • компонент из packages/ui начинает импортировать не из публичного входа пакета, а из внутреннего файла src/internal/...
  • потом вы рефакторите внутренности, и ломаются потребители
  • Решение:

  • определить публичный API пакета (обычно src/index.ts)
  • запретить deep imports из пакета, оставив разрешённым только импорт из корня
  • Пример идеи через no-restricted-imports (конкретные паттерны подстроите под вашу структуру):

    #### Защита от циклических зависимостей

    Проблема:

  • циклы между компонентами и утилитами постепенно появляются незаметно
  • это усложняет сборку, тестирование и рефакторинг
  • Решение:

  • включить проверку циклов на уровне ESLint
  • Пример:

    #### Запрет “тяжёлых” или запрещённых зависимостей

    Проблема:

  • в UI пакет “протаскивают” зависимости, которые увеличивают бандл или нарушают архитектуру
  • Решение:

  • запретить конкретные импорты, например:
  • Примечание: это не универсальное правило, но сам паттерн полезен.

    #### Удаление неиспользуемых импортов как правило гигиены

    Неиспользуемые импорты:

  • захламляют дифф
  • мешают читать код
  • иногда ломают tree-shaking
  • Пример:

    Как подружить SonarQube и ESLint, чтобы не было шума

    И SonarQube, и ESLint умеют находить часть пересекающихся проблем. Цель — не “вырезать” пересечение полностью, а сделать так, чтобы:

  • разработчик ловил проблемы быстро (ESLint локально)
  • PR блокировался по понятным критериям (SonarQube Quality Gate)
  • Практичная стратегия разделения:

  • ESLint:
  • - импорты и архитектурные ограничения - автофиксы (unused imports) - командные соглашения
  • SonarQube:
  • - Quality Gate на новом коде (bugs/vulnerabilities) - maintainability сигналы (complexity, duplications) - единая точка отчётности

    Таблица, как обычно распределяют ответственность:

    | Тип проблемы | Где ловим в первую очередь | Почему | |---|---|---| | Неиспользуемые импорты | ESLint | автофикс и быстрый фидбек | | Циклы импортов | ESLint | проще enforce’ить как “нельзя никогда” | | Глубокие импорты (нарушение public API) | ESLint | правило зависит от вашей структуры | | Дублирование, сложность | SonarQube | удобнее как метрика и тренд | | Bugs/Vulnerabilities/Hotspots | SonarQube | удобнее как gate на PR и процесс review |

    Ключевой принцип: если ESLint уже “железно” блокирует класс проблемы и даёт автофикс, нет смысла строить процесс, где SonarQube становится единственной точкой обнаружения.

    Настройка SonarQube проекта пакета, чтобы TypeScript контекст был корректным

    В прошлой статье мы уже сделали основу sonar-project.properties. Для TS-пакетов добавьте явную привязку к tsconfig.json пакета, если у вас есть нестандартная структура, references или алиасы.

    Пример для packages/ui/sonar-project.properties (добавка к уже существующему шаблону):

    Практическая проверка:

  • анализ запускается с sonar.projectBaseDir=packages/ui
  • тогда tsconfig.json корректно находится в корне пакета
  • Если вы запускаете анализ из корня репозитория, но сканируете пакет через sonar.projectBaseDir, идея та же: все пути должны быть валидны относительно baseDir.

    Как это уложить в Turborepo пайплайн

    Чтобы SonarQube не анализировал “в вакууме”, обычно фиксируют зависимость:

  • sonar зависит от test (чтобы был lcov.info)
  • часто полезно, чтобы sonar зависел и от lint (чтобы базовая гигиена была пройдена раньше, а PR не блокировался “мелочами” уже на этапе quality gate)
  • Ссылки по Turborepo:

  • Turborepo Documentation
  • Конкретную сборку turbo.json и запуск анализа “только затронутых пакетов” логично делать на следующем шаге внедрения, когда вы уже стабилизировали правила.

    Практический чеклист для UI Kit

  • На уровне TypeScript:
  • - в каждом анализируемом пакете есть свой tsconfig.json - include содержит только исходники, exclude выкидывает артефакты - алиасы (paths) доступны пакетам через extends
  • На уровне ESLint:
  • - есть правила, защищающие public API (запрет deep imports) - включена защита от циклов - включено удаление неиспользуемых импортов - запрещены критичные зависимости или направления импортов (по вашим договорённостям)
  • На уровне SonarQube:
  • - анализ идёт по пакету (проект на пакет) - sonar.sources/tests/exclusions корректны - coverage подключён, а New Code и Quality Gate реально блокируют плохие изменения

    Дальше, когда правила стабилизированы, имеет смысл автоматизировать запуск в CI: запускать анализ только для затронутых пакетов и “декорировать” PR результатами Quality Gate.

    6. Покрытие тестами: Jest/Vitest, lcov и корректная агрегация по workspace

    Покрытие тестами: Jest/Vitest, lcov и корректная агрегация по workspace

    Покрытие тестами в SonarQube для фронтенда почти всегда “ломается” не из‑за самих тестов, а из‑за путей: где лежит lcov.info, какие пути внутри него, и как SonarScanner сопоставляет их с sonar.sources при анализе пакета.

    В предыдущих статьях мы:

  • выбрали стратегию проект SonarQube на пакет в Turborepo
  • настроили границы анализа через sonar-project.properties
  • Теперь задача — сделать покрытие воспроизводимым и честным для каждого workspace-пакета (packages/ui, packages/tokens и т.д.), чтобы Quality Gate на New Code не превращался в случайность.

    !Поток: тесты генерируют lcov в каждом пакете, SonarScanner читает его и отправляет метрики в проект пакета

    Что такое lcov и как SonarQube читает покрытие

    lcov.info — текстовый отчёт покрытия (формат LCOV), который обычно генерируется Istanbul-экосистемой. Внутри файла ключевое — строки SF:..., они указывают путь к исходному файлу.

    SonarQube для JavaScript/TypeScript:

  • не “вычисляет coverage сам”
  • читает внешний отчёт lcov.info
  • пытается сопоставить пути из SF: с файлами, которые попали в анализ (sonar.sources)
  • Если сопоставление не получилось, вы увидите типичный симптом: coverage = 0%, хотя тесты реально покрывают код.

    Документация:

  • SonarQube JS/TS coverage
  • Две модели покрытия в монорепо: по пакетам или объединённое

    Покрытие по пакетам (рекомендуемая модель для UI Kit)

    Идея: каждый пакет генерирует свой coverage/lcov.info, и анализ SonarQube этого пакета использует только его.

    Плюсы:

  • coverage честно соответствует пакету
  • проще диагностика путей
  • проще Quality Gate и ownership
  • Минусы:

  • нужно прогонять тесты пакетов (но Turborepo хорошо оптимизирует это кэшем и фильтрами)
  • Объединённое покрытие (нужно редко)

    Идея: вы делаете один SonarQube-проект на весь монорепо и собираете единый lcov.

    Риски:

  • чаще возникают проблемы сопоставления путей
  • метрика coverage “размывается” между пакетами
  • Для курса (и для типичного UI Kit) дальше считаем, что вы используете покрытие по пакетам.

    Базовое правило, чтобы coverage не стал 0%

    Ваша конфигурация должна одновременно обеспечивать 3 условия:

  • Файл coverage/lcov.info существует к моменту запуска SonarScanner.
  • Внутри lcov.info пути в SF: сопоставимы с деревом исходников пакета.
  • SonarScanner запускается с правильной базовой директорией пакета (либо через запуск из папки пакета, либо через sonar.projectBaseDir).
  • Практический критерий: если анализ пакета идёт с projectBaseDir=packages/ui, то идеально, когда в lcov.info пути выглядят как SF:src/components/Button.tsx (или эквивалентно, но обязательно внутри пакета).

    Jest: как включить lcov на уровне пакета

    Официальная документация Jest по покрытию:

  • Jest: Code Coverage
  • Минимальная настройка (подходит большинству UI пакетов)

    Если у вас Jest конфиг в пакете (packages/ui/jest.config.ts), то базовая цель:

  • включить lcov
  • писать отчёт в coverage/ внутри пакета
  • Пример packages/ui/jest.config.ts:

    Скрипт тестов пакета

    В packages/ui/package.json:

    Рекомендация: в CI и для Sonar используйте отдельную команду, которая гарантированно генерирует lcov.info (например, test:coverage).

    Vitest: как включить lcov на уровне пакета

    Официальная документация:

  • Vitest: Coverage
  • Vitest может делать coverage через провайдер v8 или istanbul. Для связки с SonarQube важен именно результат lcov.

    Пример packages/ui/vitest.config.ts:

    Скрипты в packages/ui/package.json:

    Как связать coverage с анализом SonarQube по пакету

    Если вы храните sonar-project.properties внутри пакета (как мы делали в прошлой статье), то минимальная привязка выглядит так:

    packages/ui/sonar-project.properties:

    Ключевой момент: sonar.javascript.lcov.reportPaths задаётся относительно базовой директории анализа.

    Turborepo: как заставить каждый пакет генерировать coverage предсказуемо

    Turborepo запускает задачи в контексте пакетов, поэтому правильная практика такая:

  • в каждом анализируемом пакете есть test:coverage, генерирующий packages/<pkg>/coverage/lcov.info
  • задача sonar зависит от test:coverage
  • Документация Turborepo:

  • Turborepo: Configuration
  • Turborepo: Filtering
  • Пример turbo.json:

    Важно: outputs для test:coverage помогает Turborepo кэшировать coverage-артефакты (если это приемлемо для вашего CI-процесса). Если вы не хотите кэшировать coverage, можно убрать outputs, но тогда вы чаще будете “платить временем”.

    Типовая команда запуска: анализируем только затронутые пакеты

    В CI для PR обычно хотят запускать тесты и sonar только для пакетов, которые затронуты изменениями.

    Концептуально это выглядит так:

  • turbo run test:coverage --filter=...
  • turbo run sonar --filter=...
  • Конкретная реализация фильтра зависит от вашего CI и политики (по git-диапазону, по списку изменённых workspaces и т.д.). Важно другое: SonarQube-проектов у вас несколько, поэтому и анализов может быть несколько — только по изменённым пакетам.

    Диагностика: почему SonarQube показывает 0% coverage

    Ниже самые частые причины именно в монорепо.

    SonarScanner не нашёл lcov.info

    Проверьте:

  • файл реально существует в момент запуска sonar-scanner
  • путь в sonar.javascript.lcov.reportPaths корректен относительно projectBaseDir
  • Мини-проверка, которую полезно добавить в CI шаг перед сканером:

    Пути в lcov.info не совпадают с тем, что Sonar считает исходниками

    Откройте первые строки coverage/lcov.info и найдите SF:.

    Хорошо:

  • SF:src/Button.tsx
  • Плохо (частая причина несовпадения):

  • SF:/home/runner/work/repo/repo/packages/ui/src/Button.tsx при том, что анализ идёт не из этой директории или sonar.projectBaseDir настроен иначе
  • SF:../../packages/ui/src/Button.tsx при том, что анализ идёт из packages/ui
  • Если SF: указывает на файлы, которые Sonar не “видит” в sonar.sources, coverage не приклеится.

    Неправильная базовая директория анализа

    Если вы запускаете сканер из корня монорепо, но анализируете пакет, обязательно используйте sonar.projectBaseDir=packages/<pkg> (или запускайте сканер, находясь в папке пакета).

    Это особенно критично в модели проект на пакет.

    Что исключать из покрытия: Jest/Vitest против Sonar

    Есть два разных слоя исключений:

  • исключения на стороне инструмента покрытия (Jest/Vitest)
  • исключения на стороне SonarQube (sonar.coverage.exclusions)
  • Практическое правило для UI Kit:

  • если файл не должен участвовать в coverage никогда, исключайте его на стороне Jest/Vitest
  • если файл должен анализироваться (issues/смеллы), но не должен портить coverage, исключайте через sonar.coverage.exclusions
  • Пример для Sonar (внутри пакета):

    Важно: sonar.exclusions и sonar.coverage.exclusions — разные вещи.

  • sonar.exclusions убирает файл из анализа полностью
  • sonar.coverage.exclusions оставляет файл в анализе, но выкидывает из расчёта покрытия
  • Рекомендованный минимальный стандарт для UI Kit пакетов

    Чтобы внедрение SonarQube не стало “охотой на баги путей”, зафиксируйте стандарт на уровне всех пакетов UI Kit.

  • В каждом пакете есть команда test:coverage, которая создаёт coverage/lcov.info в корне пакета.
  • В каждом пакете sonar-project.properties содержит sonar.javascript.lcov.reportPaths=coverage/lcov.info.
  • Анализ пакета запускается с базой пакета:
  • - либо запуском из папки пакета - либо через sonar.projectBaseDir=packages/<pkg>
  • Stories и витрины либо исключены из coverage договорённостью, либо покрываются так же, как продуктовый код, но правило едино для всех.
  • Когда этот минимум стабилен, Quality Gate по покрытию на New Code начинает работать как управляемый процесс, а не как “лотерея CI”.

    Что дальше

    Следующий шаг внедрения в Turborepo — сделать запуск SonarScanner по пакетам полностью автоматическим в CI:

  • определять затронутые пакеты
  • запускать test:coverage и sonar только для них
  • публиковать результаты в соответствующие SonarQube-проекты и получать PR decoration
  • 7. Интеграция с CI (GitHub Actions/GitLab), кэш Turborepo и Quality Gate

    Интеграция с CI (GitHub Actions/GitLab), кэш Turborepo и Quality Gate

    В прошлых статьях курса мы настроили:

  • границы анализа (sonar.sources, sonar.tests, sonar.exclusions)
  • анализ по пакетам (проект SonarQube на пакет)
  • покрытие через lcov.info на уровне workspace
  • Теперь нужно собрать это в устойчивый CI-процесс, который:

  • запускает тесты и анализ только для затронутых пакетов
  • ускоряется за счёт кэша Turborepo и кэша пакетного менеджера
  • блокирует merge через Quality Gate
  • !Общая картина: как изменения в пакете проходят тесты, анализ и проверку Quality Gate

    Что именно мы хотим получить от CI

  • Быстро: не гонять весь монорепо, если поменяли один пакет.
  • Воспроизводимо: всегда есть coverage/lcov.info до запуска SonarScanner.
  • Управляемо: merge блокируется по Quality Gate на New Code.
  • Ключевая идея из предыдущих статей остаётся той же: проект SonarQube = пакет (packages/ui, packages/tokens и т.д.), и CI публикует анализ в соответствующий проект.

    Минимальные секреты и переменные окружения

    В CI обычно нужны:

    | Что | Где хранить | Зачем | |---|---|---| | SONAR_HOST_URL | secrets/variables | URL вашего SonarQube сервера | | SONAR_TOKEN | secrets/variables | токен CI-пользователя для публикации анализа | | TURBO_TOKEN | secrets/variables | токен для Remote Cache (если используете) | | TURBO_TEAM | secrets/variables | команда/пространство Remote Cache (если используете) |

    Ссылки:

  • SonarScanner CLI
  • Turborepo Remote Caching
  • Как запускать анализ только для затронутых пакетов

    Есть два устойчивых подхода.

    Подход A: фильтрация задач через Turborepo

    Turborepo умеет запускать задачи только для пакетов, затронутых изменениями.

  • Вы запускаете тесты с coverage по фильтру (по git-диапазону).
  • Затем запускаете Sonar-анализ по тем же пакетам.
  • Ссылки:

  • Turborepo Filtering
  • Важно: чтобы фильтрация по git-диапазону работала корректно в CI, репозиторий должен быть получен с историей (или хотя бы с нужными коммитами), иначе Turborepo не сможет корректно вычислить, что поменялось.

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

  • Скрипт смотрит git diff и находит, какие packages/<name>/... затронуты.
  • CI делает цикл по списку и запускает turbo run test:coverage --filter=<package> и затем sonar-scanner для каждого пакета.
  • Этот подход проще дебажить и не зависит от тонкостей фильтров, но требует небольшого скрипта.

    Ниже в примерах CI будет использован именно этот подход, потому что он обычно понятнее на старте внедрения.

    Кэш в CI: что кэшировать, а что нет

    Кэш Turborepo

    Turborepo кэширует результаты задач (например, test:coverage) в директории .turbo.

  • Если вы используете локальный кэш в CI, имеет смысл кэшировать .turbo между запусками pipeline.
  • Если вы используете Remote Cache, то кэш будет разделяемым между CI-раннерами и разработчиками (обычно это самый заметный прирост скорости на больших монорепо).
  • Ссылки:

  • Turborepo Caching
  • Turborepo Remote Caching
  • Практическая рекомендация:

  • кэшируйте .turbo и кэш пакетного менеджера
  • не кэшируйте coverage/ как артефакт между pipeline, если у вас нет явной причины (coverage должен соответствовать текущему коду)
  • Кэш пакетного менеджера

    Для фронтенд монорепо это обычно самый дешёвый и полезный кэш.

  • Для pnpm кэшируйте pnpm store.
  • Для yarn кэшируйте .yarn/cache.
  • Для npm кэшируйте ~/.npm.
  • Quality Gate в CI: как сделать merge-блокировку

    Quality Gate живёт на стороне SonarQube Server, но CI должен:

  • дождаться результата анализа
  • упасть, если Quality Gate не пройден
  • В SonarScanner CLI это обычно делается параметром ожидания Quality Gate.

    Пример запуска (общая идея):

  • анализ публикуется
  • сканер ждёт вычисления Quality Gate
  • сканер возвращает ненулевой код выхода, если gate не пройден
  • Документация:

  • Quality Gates
  • Важный нюанс про PR/MR decoration:

  • полноценный анализ Pull Request и публикация комментариев/статусов в интерфейс PR/MR в self-hosted SonarQube зависят от редакции и интеграции
  • даже без decoration вы всё равно можете блокировать merge через статус pipeline, если ваш CI падает при провале Quality Gate
  • Пример для GitHub Actions

    Скрипт определения изменённых пакетов

    Пример scripts/changed-packages.sh:

    Что важно для корректной работы:

  • в workflow нужен fetch-depth: 0 или корректная подгрузка base-ветки
  • BASE_REF должен существовать локально (обычно это origin/main)
  • Workflow

    Пример .github/workflows/sonar.yml (ориентируйтесь и адаптируйте под свой пакетный менеджер и ваши задачи):

    Ссылки:

  • GitHub Actions documentation
  • actions/checkout
  • actions/cache
  • Практические замечания:

  • sonar-scanner должен быть доступен в runner. Вы можете поставить его как зависимость, либо использовать готовый способ установки из документации SonarScanner.
  • -Dsonar.projectBaseDir и -Dproject.settings должны соответствовать вашей стратегии конфиг в пакете.
  • Если изменённых пакетов нет, job пропускает анализ.
  • Пример для GitLab CI

    Ниже пример .gitlab-ci.yml, который:

  • запускается на Merge Request
  • кэширует пакетный менеджер и .turbo
  • анализирует только изменённые packages/*
  • Ссылки:

  • GitLab CI/CD documentation
  • GitLab CI rules
  • Практические замечания:

  • SONAR_HOST_URL и SONAR_TOKEN заведите в GitLab как CI/CD Variables (masked, protected при необходимости).
  • Если вы используете pnpm, обычно выгодно отдельно кэшировать pnpm store, но конкретная стратегия зависит от вашего runner и его файловой системы.
  • Как уложить это в turbo.json

    Чтобы Sonar-анализ всегда запускался после генерации покрытия, фиксируем зависимость задач.

    Пример turbo.json (идея из прошлой статьи про coverage, но теперь это становится обязательным для CI):

    Почему outputs: [] у sonar:

  • Sonar-анализ — это публикация результатов на сервер, а не артефакт, который имеет смысл кэшировать
  • вам важнее кэшировать тесты/сборку, чем повторно использовать «результат отправки»
  • Типовые проблемы в CI и как их быстро диагностировать

    Sonar не видит New Code как ожидается

    Частая причина: CI скачал репозиторий без истории, и Sonar/Turbo не могут корректно сравнить ветки.

    Решение:

  • GitHub Actions: fetch-depth: 0 в actions/checkout
  • GitLab: GIT_DEPTH: "0"
  • Coverage в SonarQube стал 0%

    Проверяйте по порядку:

  • В CI реально появился packages/<pkg>/coverage/lcov.info.
  • В sonar-project.properties указан правильный sonar.javascript.lcov.reportPaths.
  • sonar.projectBaseDir указывает на папку пакета.
  • Слишком долго ждём Quality Gate

    Причины:

  • сервер SonarQube перегружен
  • вы запускаете много анализов параллельно (по числу пакетов)
  • Решения:

  • ограничить параллельность (запускать анализ пакетов последовательно)
  • увеличить ресурсы сервера
  • анализировать только действительно затронутые пакеты
  • Итог: минимальный стандарт CI для UI Kit монорепо

  • В CI вычисляем список затронутых packages/*.
  • Для каждого пакета:
  • 1. запускаем test:coverage, чтобы получить coverage/lcov.info 2. запускаем sonar-scanner с:

    - sonar.projectBaseDir=packages/<pkg> - project.settings=packages/<pkg>/sonar-project.properties - ожиданием Quality Gate

  • Кэшируем:
  • кэш пакетного менеджера
  • .turbo (и при возможности включаем Remote Cache)
  • После этого SonarQube перестаёт быть «отчётом после факта» и становится реальным стоп-краном качества для PR/MR.