Монорепозиторий: типичные ошибки и как их избежать
Монорепозиторий упрощает совместную разработку пакетов, но быстро превращается в хрупкую сборку, если зависимости объявлены неточно, а CI проверяет не тот код. Разбираем частые ошибки на примере pnpm 9.x и способы их исправить.

Один репозиторий — не значит одна большая папка
Монорепозиторий часто начинает тормозить не из-за размера, а из-за неявных связей: приложение использует пакет, которого нет в его зависимостях, локальная сборка проходит за счёт общего окружения, а CI не замечает изменения в связанном модуле. Ниже — типичные ошибки на примере pnpm 9.x и рабочих пространств. Сами принципы применимы и к другим менеджерам пакетов, но команды и настройки у них отличаются.
В примерах предполагается, что в корне уже есть файл pnpm-workspace.yaml, а пакеты лежат в каталогах apps и packages. Версии pnpm, Node.js и инструментов сборки стоит закрепить в проекте и проверять вместе с конфигурацией CI.
Ошибка 1. Пакет использует зависимость, которую не объявил
Типичный сценарий: приложению нужен пакет для сборки или тестов, но его нет в package.json приложения. Локально всё работает, потому что зависимость установлена в корне или доступна из соседнего пакета. В чистом окружении CI или после изменения структуры репозитория сборка падает — либо, что хуже, подхватывает не ту версию.
В монорепозитории декларация в корне не заменяет декларацию у потребителя. Если пакет импортируется приложением напрямую, приложение должно объявить его у себя. То же относится к внутренним библиотекам: зависимость между пакетами должна быть видна менеджеру пакетов.
{
"name": "@acme/web",
"dependencies": {
"@acme/ui": "workspace:*"
}
}Протокол workspace: указывает pnpm использовать локальный пакет рабочего пространства. Если нужного пакета нет в монорепозитории, установка не должна молча подменять его опубликованной версией. При публикации pnpm преобразует такие диапазоны в обычные версии.
Ошибка 2. Локальная сборка скрывает границы пакетов
Если пакет работает только благодаря файлам из соседнего каталога, его нельзя независимо установить и собрать. Часто причина — импорт исходников напрямую через относительный путь вроде ../../packages/ui/src вместо зависимости на пакет. Так потребитель начинает знать внутреннюю структуру библиотеки, а сборщик может обрабатывать её не так, как опубликованный пакет.
У пакета должны быть собственное имя, зависимости и публичная точка входа. Потребитель импортирует его по имени пакета, например @acme/ui, а не по пути к файлу исходника. Это дисциплинирует границы и обнаруживает ошибки до публикации.
- Проверьте, что пакет объявлен в
pnpm-workspace.yaml. - Добавьте внутренний пакет в зависимости потребителя через
workspace:. - Собирайте пакет так, как его будет использовать приложение: через объявленный публичный интерфейс.
- Убедитесь, что изменения в исходниках пакета действительно запускают проверку его потребителей.
Термин. Граф зависимостей — схема того, какие пакеты зависят друг от друга. Менеджер пакетов использует её, чтобы связать локальные пакеты и выстроить порядок некоторых рекурсивных команд.
Как объявлять зависимости: три варианта
Внутренний пакет можно подключить разными способами. Для pnpm в одном рабочем пространстве безопаснее явно использовать протокол workspace:: он помогает не перепутать локальный пакет с одноимённой опубликованной версией.
| Способ | Когда подходит | Цена и риск |
|---|---|---|
workspace:* | Пакет всегда должен браться из текущего монорепозитория | Чёткая локальная связь; нужно проверить поведение публикации и наличие пакета в workspace |
Диапазон версии, например ^1.2.0 | Потребитель должен работать с опубликованной версией или пакет живёт отдельно | Версия может отличаться от локальных исходников; легко тестировать не тот код |
| Относительный импорт исходника | Редкие специальные сценарии сборки | Сильная связность с устройством репозитория; потребитель может не работать вне монорепозитория |
Выбирайте workspace:, когда приложение и библиотека меняются и проверяются вместе. Диапазон опубликованных версий нужен, когда пакет действительно выпускается независимо. Прямой импорт чужих исходников оставляйте только для обоснованного случая, иначе он размывает границы.
Если пакет нельзя собрать и проверить без случайных зависимостей из корня, проблема не в CI. У пакета не описаны его настоящие границы.
Ошибка 3. CI проверяет только изменённое приложение
Изменение библиотеки может сломать приложение, которое её использует, хотя файлы приложения не менялись. Обратная ситуация тоже возможна: запуск тестов во всех пакетах после каждой правки тратит время и ресурсы без необходимости. Нужен не выбор между «проверять всё» и «проверять только одну папку», а понятное правило обхода зависимостей.
В pnpm 9.x рекурсивные команды позволяют выполнять скрипт в пакетах рабочего пространства. Например, pnpm -r run test запускает скрипт test рекурсивно там, где он определён. Для проверки отдельного пакета используйте фильтр: pnpm --filter @acme/web test. Фильтр охватывает выбранный пакет; если нужно учитывать связанные пакеты, проверьте синтаксис фильтрации по зависимостям в документации pnpm и закрепите правило в CI.
Не подменяйте такую проверку самодельным списком папок, если связи между пакетами уже описаны в манифестах. Список быстро устаревает: новый потребитель забыли добавить, и CI снова даёт ложное чувство безопасности.
Ошибка 4. Lock-файл не проходит проверку на чистой установке
Lock-файл — не побочный артефакт. Он фиксирует разрешённое дерево зависимостей. Если разработчик изменил манифест, но не включил согласованные изменения lock-файла, установка в CI может завершиться ошибкой или использовать не то дерево, что локальная машина.
В монорепозитории особенно важно хранить единый lock-файл в корне и обновлять его вместе с изменениями зависимостей. Не удаляйте его перед сборкой «для надёжности»: это превращает проверку кода в повторное разрешение версий. Установка с нуля должна проходить именно на той версии pnpm, которую проект использует.
Что ломается и как это заметить
- Работает у автора, падает в CI. Проверьте, объявлены ли все используемые пакеты в манифесте конкретного потребителя, а не только в корне.
- В CI собирается старая версия библиотеки. Проверьте, подключён ли пакет через
workspace:и не указывает ли диапазон версии на опубликованный пакет. - Правка библиотеки не запускает тесты приложения. Проверьте граф зависимостей и настройку фильтрации задач в CI.
- После обновления манифеста установка не проходит в режиме замороженного lock-файла. Пересоздайте lock-файл штатной командой в закреплённом окружении и включите изменение в тот же коммит.
Проверяйте не только успешный запуск команды, но и то, какие пакеты она выбрала. Иначе слишком узкий фильтр выглядит как быстрая проверка, хотя важная часть графа остаётся без тестов.
Следующий шаг: проверьте репозиторий на чистом окружении
Начните с трёх вещей: закреплённой версии pnpm, явных зависимостей между пакетами и CI-команды, которая проверяет выбранные пакеты вместе с нужными связями. Затем сделайте контрольный прогон из чистого checkout — без локальных артефактов сборки и случайно установленных зависимостей. После этого полезно измерить длительность проверок и посмотреть, какие пакеты реально запускаются при изменении одной библиотеки. Инвалидация кэша и стратегия публикации — отдельные темы; здесь главное убедиться, что граф зависимостей честно описывает проект.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: pnpm: Workspaces, pnpm: Filtering
Частые вопросы
Нужно ли объявлять зависимость в каждом пакете, если она уже есть в корне?
Когда для внутреннего пакета использовать workspace:*?
Можно ли импортировать файлы другого пакета по относительному пути?
Почему тесты проходят локально, но не в CI?
Нужно ли запускать тесты во всех пакетах после каждого изменения?
С чего начать, если монорепозиторий уже стал хрупким?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.