Как оценить радиус изменений npm-пакета до публикации

Практический процесс для multi-repo проектов: найти потребителей, восстановить реальные цепочки, учесть lock-файлы, локальные links и Git-контекст.

npmграф зависимостейrelease engineering
В этой статье10 разделов

Представьте: вы изменили @acme/design-tokens, тесты пакета проходят, версия готова к публикации. Остался простой вопрос — какие проекты нужно проверить до релиза?

В одном монорепозитории ответ часто дают граф сборки и CI. А вот в нескольких репозиториях начинается расследование: поиск по package.json, просмотр lock-файлов, проверка локальных связей, переключение веток и попытка вспомнить, какой проект на какую версию действительно смотрит.

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

Поиск имени и оценка влияния решают разные задачи

Поиск отвечает на вопрос: «Где встречается имя пакета?» Оценка влияния должна ответить сразу на четыре других:

  1. Какие проекты являются прямыми и транзитивными потребителями?
  2. Какая версия запрошена и какая реально зафиксирована?
  3. Не переопределена ли зависимость воркспейсом, символьной ссылкой или локальным путём?
  4. В каком Git-состоянии находится каждый потребитель прямо сейчас?

Удобно держать в голове такую модель:

impact = consumers × dependency paths × resolved versions × repository state

Это не формула для вычисления числа. Это напоминание: отсутствие одного измерения делает вывод ненадёжным.

Например, поиск не найдёт прямого упоминания design-tokens в приложении, если цепочка выглядит так:

storefront
└── @acme/ui-kit
    └── @acme/design-tokens

Но изменение токенов всё равно попадёт в storefront. Значит, единицей анализа должно быть не совпадение в файле, а путь от проекта до изменяемого пакета.

Сначала зафиксируйте вопрос релиза

До обхода репозиториев запишите исходные данные:

  • имя и текущую версию пакета;
  • планируемую версию или диапазон;
  • тип изменения: API, рантайм поведение, стили, типы или сборка;
  • корни директорий, в которых лежат потенциальные потребители;
  • менеджер пакетов и registry, если он отличается от публичного npm.

SemVer помогает потребителю решить, допустима ли версия по диапазону. Он не говорит, совместимо ли конкретное изменение с конкретным кодом. Даже patch-релиз может затронуть визуальные снапшоты, генерацию типов или предположения сборщика.

Шаг 1. Используйте манифесты как список кандидатов

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

PACKAGE_NAME='@acme/design-tokens'
PROJECTS_ROOT='/path/to/projects'

rg --fixed-strings "\"$PACKAGE_NAME\"" "$PROJECTS_ROOT" \
  --glob 'package.json' \
  --glob '!**/node_modules/**'

Результат полезен, но ограничен. Он показывает декларации в dependencies, devDependencies, peerDependencies и других полях, однако не восстанавливает транзитивные цепочки.

Транзитивные зависимости — это пакеты, которые проект получает не напрямую, а через одну или несколько других зависимостей.

Для каждого найденного проекта сохраните минимум:

Поле Зачем оно нужно
Project Конкретный репозиторий или воркспейс
Requested Диапазон из манифеста
Relationship direct, dev, peer или optional
Path Цепочка до изменяемого пакета
Evidence manifest, lockfile или локальная ссылка

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

Шаг 2. Сравните объявленную и зафиксированную версии

Манифест хранит намерение, lock-файл — выбранное состояние установки. В документации npm package-lock.json описан как представление конкретного дерева зависимостей, достаточное для воспроизводимой установки: package-lock.json.

Зафиксированная версия не всегда совпадает с тем, что установлено сейчас: после переключения ветки зависимости могли ещё не переустановить. Кроме того, npm ci --omit=dev оставляет зависимости для разработки в lock-файле, но не устанавливает их на диск. Если важно понять, какой код реально использует проект, проверьте установленное дерево отдельно. Подробнее — в документации npm об исключении зависимостей при установке.

Практически это означает, что для каждого пути нужны два значения:

  • requested: например, ^2.1.0;
  • resolved: например, 2.3.4.

Они отвечают на разные вопросы. Диапазон может уже допускать новую версию, но текущий lock-файл продолжит фиксировать старую. Или наоборот: несколько пакетов запрашивают совместимые диапазоны, но из-за peer-зависимостей в дереве присутствуют разные экземпляры.

Пример отчёта:

Project Requested Resolved Path Вывод
admin-app ^2.1.0 2.3.4 direct проверить обновление lock-файла
storefront — 2.2.0 via ui-kit сначала проверить промежуточный пакет
docs workspace:* local linked тестировать локальный исходник

Простой поиск имени по lock-файлу тоже недостаточен. В нём могут оставаться несколько вариантов пакета или записи, которые не достижимы от корня текущего проекта. Нужен именно обход связей от проекта, а не подсчёт совпадений.

Шаг 3. Проверьте локальные источники отдельно

Локальная зависимость легко меняет смысл результата. Диапазон в манифесте может выглядеть как registry-зависимость, а фактически проект использует workspace, file:, link:, portal:, Yarn link или обычный symlink.

У менеджеров пакетов эти механизмы различаются. Полезные отправные точки: протоколы Yarn link: и portal:, а также workspace-протокол pnpm.

Для каждой локальной связи проверьте:

  1. Куда ведёт канонический путь после разрешения symlink.
  2. Совпадает ли найденный package.json с пакетом, который вы собираетесь публиковать.
  3. Не является ли ссылка технической частью хранилища package manager.
  4. Какая ветка открыта в связанном репозитории.

Последний пункт особенно важен: локальный link может незаметно подключить незакоммиченные изменения и создать уверенность, которой не будет после установки из registry.

Шаг 4. Добавьте Git-контекст к графу

Список проектов без веток быстро устаревает. Один и тот же репозиторий может быть открыт на feature-ветке, существовать в нескольких worktree или содержать незакоммиченные изменения.

Минимальная проверка для каждого потребителя:

git -C "$PROJECT_DIR" status --short --branch
git -C "$PROJECT_DIR" worktree list

git worktree позволяет держать несколько рабочих деревьев одного репозитория; это полноценная часть состояния, а не просто ещё одна директория: документация Git.

Добавьте в отчёт:

  • активную ветку;
  • dirty/clean статус изменений;
  • основной это checkout или связанный worktree;
  • соответствует ли ветка сценарию, который вы собираетесь проверять.

Если проект находится не на ожидаемой ветке, результат анализа нельзя автоматически считать результатом для main.

Шаг 5. Превратите карту в план действий

Теперь проекты можно классифицировать:

Сигнал Приоритет Следующее действие
Прямая зависимость и несовместимый диапазон высокий обновить диапазон, установить, запустить проверки
Транзитивный путь через внутреннюю библиотеку высокий проверить и выпустить промежуточную библиотеку первой
Локально связанный пакет средний проверить на чистом checkout и registry-версии
Путь найден только в lock-файле требует разбора подтвердить достижимость от корня проекта
Неожиданная ветка или dirty статус блокирующий привести репозиторий в понятное состояние

Хороший отчёт не просто перечисляет потребителей. Он объясняет, почему проект попал в список и что с ним делать.

Минимальный воспроизводимый процесс

Если свести всё к короткому чек-листу, получится семь шагов:

  1. Зафиксировать пакет, версию и характер изменения.
  2. Найти прямых кандидатов по манифестам.
  3. Построить транзитивные пути между локальными пакетами.
  4. Сопоставить requested и resolved версии по lock-файлам.
  5. Выявить workspaces, links и реальные файловые пути.
  6. Добавить ветку, worktree и dirty статус.
  7. Сформировать порядок обновления и команды проверки.

Для пары репозиториев этого достаточно сделать вручную. Сохраните результат в таблице и обновляйте перед каждым релизом общего пакета.

Когда автоматизация необходима

Автоматизация становится оправданной не из-за прямых зависимостей в одном package.json, а из-за количества пересечений:

  • несколько корней с репозиториями;
  • npm, Yarn и pnpm в одной рабочей среде (редкость, но не исключение);
  • прямые, транзитивные и lockfile-only пути;
  • локальные связи (симлинки) и воркспейсы;
  • параллельные ветки и worktree;
  • повторяющиеся действия для нескольких потребителей.

В какой-то момент результатом работы становится не очередной вывод скрипта, а интерактивная карта: проект, путь, версия, ветка и действие должны находиться в одной строке.

Именно в этот момент я перенёс описанный процесс внутрь IDE на которой работаю. Так появился Dependency Radar для WebStorm: он начинает с открытого пакета, сканирует заданные корни, объединяет манифесты, lock-файлы и локальные связи, добавляет Git-контекст и позволяет перейти от найденного пути к действию.

Плагин не заменяет тесты, review или решение о SemVer. Он автоматизирует сбор доказательств перед этими решениями. Для небольшого монорепозитория это может быть лишним. Для multi-repo среды или микросервисной архитектуры — способ перестать заново проводить одно и то же расследование перед каждым релизом или правками фичи.

Чек-лист перед публикацией

Перед нажатием publish убедитесь, что можете ответить «да» на пять вопросов:

  • найдены прямые и транзитивные потребители;
  • для каждого пути известны requested и resolved версии;
  • локальные links и workspaces разрешены до реального проекта;
  • ветки, worktree и незакоммиченные изменения учтены;
  • для затронутых проектов определены порядок и команды проверки.

Главная цель не в том, чтобы получить самый красивый граф. Цель — понимать, на каких данных основано решение о релизе и какой следующий шаг нужен для каждого потребителя.

Дисклеймер: при подготовке и редактировании этой статьи использовалась нейросеть.