Как оценить радиус изменений npm-пакета до публикации
Практический процесс для multi-repo проектов: найти потребителей, восстановить реальные цепочки, учесть lock-файлы, локальные links и Git-контекст.
В этой статье10 разделов
Представьте: вы изменили @acme/design-tokens, тесты пакета проходят, версия готова к публикации. Остался простой вопрос — какие проекты нужно проверить до релиза?
В одном монорепозитории ответ часто дают граф сборки и CI. А вот в нескольких репозиториях начинается расследование: поиск по package.json, просмотр lock-файлов, проверка локальных связей, переключение веток и попытка вспомнить, какой проект на какую версию действительно смотрит.
Эта статья — о том, как провести такую проверку системно. Даже если вы не будете её автоматизировать, в конце останется воспроизводимый процесс и понятный формат отчёта.
Поиск имени и оценка влияния решают разные задачи
Поиск отвечает на вопрос: «Где встречается имя пакета?» Оценка влияния должна ответить сразу на четыре других:
- Какие проекты являются прямыми и транзитивными потребителями?
- Какая версия запрошена и какая реально зафиксирована?
- Не переопределена ли зависимость воркспейсом, символьной ссылкой или локальным путём?
- В каком 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.
Для каждой локальной связи проверьте:
- Куда ведёт канонический путь после разрешения symlink.
- Совпадает ли найденный
package.jsonс пакетом, который вы собираетесь публиковать. - Не является ли ссылка технической частью хранилища package manager.
- Какая ветка открыта в связанном репозитории.
Последний пункт особенно важен: локальный 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 статус | блокирующий | привести репозиторий в понятное состояние |
Хороший отчёт не просто перечисляет потребителей. Он объясняет, почему проект попал в список и что с ним делать.
Минимальный воспроизводимый процесс
Если свести всё к короткому чек-листу, получится семь шагов:
- Зафиксировать пакет, версию и характер изменения.
- Найти прямых кандидатов по манифестам.
- Построить транзитивные пути между локальными пакетами.
- Сопоставить requested и resolved версии по lock-файлам.
- Выявить workspaces, links и реальные файловые пути.
- Добавить ветку, worktree и dirty статус.
- Сформировать порядок обновления и команды проверки.
Для пары репозиториев этого достаточно сделать вручную. Сохраните результат в таблице и обновляйте перед каждым релизом общего пакета.
Когда автоматизация необходима
Автоматизация становится оправданной не из-за прямых зависимостей в одном package.json, а из-за количества пересечений:
- несколько корней с репозиториями;
- npm, Yarn и pnpm в одной рабочей среде (редкость, но не исключение);
- прямые, транзитивные и lockfile-only пути;
- локальные связи (симлинки) и воркспейсы;
- параллельные ветки и worktree;
- повторяющиеся действия для нескольких потребителей.
В какой-то момент результатом работы становится не очередной вывод скрипта, а интерактивная карта: проект, путь, версия, ветка и действие должны находиться в одной строке.
Именно в этот момент я перенёс описанный процесс внутрь IDE на которой работаю. Так появился Dependency Radar для WebStorm: он начинает с открытого пакета, сканирует заданные корни, объединяет манифесты, lock-файлы и локальные связи, добавляет Git-контекст и позволяет перейти от найденного пути к действию.
Плагин не заменяет тесты, review или решение о SemVer. Он автоматизирует сбор доказательств перед этими решениями. Для небольшого монорепозитория это может быть лишним. Для multi-repo среды или микросервисной архитектуры — способ перестать заново проводить одно и то же расследование перед каждым релизом или правками фичи.
Чек-лист перед публикацией
Перед нажатием publish убедитесь, что можете ответить «да» на пять вопросов:
- найдены прямые и транзитивные потребители;
- для каждого пути известны requested и resolved версии;
- локальные links и workspaces разрешены до реального проекта;
- ветки, worktree и незакоммиченные изменения учтены;
- для затронутых проектов определены порядок и команды проверки.
Главная цель не в том, чтобы получить самый красивый граф. Цель — понимать, на каких данных основано решение о релизе и какой следующий шаг нужен для каждого потребителя.
Дисклеймер: при подготовке и редактировании этой статьи использовалась нейросеть.