Менее известный, чем его брат, npm install
, с npm clean-install
(сокращенно npm ci
), ваш процесс CI/CD становится более надежным. Вот как это использовать.
Что такое нпм?
Каждый разработчик, который работал с чем-либо, связанным с Интернетом, использовал или слышал о диспетчере пакетов Node: npm. npm — это утилита командной строки, которая поставляется с Node.js. Его основная функция — установка модулей JavaScript из официального репозитория Node.
Типичный вызов установки:
$ npm install -s MODULE_NAME
Это делает ряд вещей:
- Ищет модуль по имени.
- Загружает и устанавливает модуль и его зависимости.
- Обновляет (или создает) package-lock.json. Этот файл называется lockfile и содержит URL-адрес и контрольную сумму каждого установленного модуля.
- Добавляет имя и версию модуля в package.json. Этот файл известен как манифест.
Ключ к воспроизводимости лежит в файле блокировки, package-lock.json
. В следующий раз, когда мы запустим npm install
, менеджер пакетов сравнит его с содержимым node_modules
, папки, содержащей все модули JavaScript для текущего проекта, и установит все отсутствующие модули. npm будет использовать package-lock.json
, чтобы убедиться, что он загружает те же файлы, что и в первый раз, даже если с тех пор были выпущены более новые совместимые версии.
Итак, что не так с установкой npm?
Если мы посмотрим внимательно, npm install
был разработан с учетом удобства разработчиков. И это показывает, что npm — один из моих любимых инструментов и одна из причин, по которой мне нравится работать с Node.
Дело в том, что алгоритм установки иногда бывает слишком хитрым. Посмотрите, что происходит, когда package-lock.json
и package.json
не синхронизированы.
Предположим, я устанавливаю новую зависимость в своем проекте Node:
$ npm install -s axios + [email protected] added 2 packages from 4 contributors and audited 2 packages in 1.269s
На моей машине все выглядит нормально, поэтому я фиксирую изменение:
$ git add mycode.js package.json $ git commit -m "add axios dependency" $ git push origin mybranch
Вы видели мою ошибку? Правильно: я забыл добавить файл блокировки в коммит. Через некоторое время, когда второй разработчик вытащит мою ветку, npm не будет знать точную версию, которую я планировал изначально. Эта информация была в файле блокировки, и я забыл включить ее в коммит.
В этот момент вы можете сказать: «Но манифест действительно включает версию модуля». Вы правы, в манифесте это указано в таком виде:
"dependencies": { "axios": "^0.21.0" }
Однако это не обязательно соответствует точной версии. Node поощряет использование схемы семантического управления версиями. Символ ^
в моем манифесте означает, что я принимаю любой второстепенный выпуск, равный или превышающий 0.21.0
. Таким образом, npm может устанавливать более новые версии, выпущенные за это время, такие как 0.21.1
, 0.22.0
или 0.23.1
, которые теоретически должны быть совместимыми, но могут и не быть.
Два источника истины
Алгоритм npm install
сначала проверяет совпадение package.json
и package-lock.json
. Если это так, npm следует только за файлом блокировки. Но если они этого не делают, npm считает манифест каноническим и соответствующим образом обновляет файл блокировки.
Такое поведение является особенностью. Кэт Марчан, разработчик, который написал package-lock.json
, а затем npm ci
, сказал, что они сделали это таким образом, когда поняли, что люди меняют зависимости вручную в package.json
.
В большинстве случаев, когда файл блокировки и манифест не совпадают, npm install
поступает правильно и получает версию, изначально предназначенную коммиттером, но нет никаких гарантий. У других разработчиков могут быть немного другие версии, что приводит к синдрому «работает на моей машине».
Что еще хуже, так это то, что артефакты, генерируемые конвейером CI/CD, будут неумолимо меняться со временем, способствуя общей нестабильности и вызывая труднодиагностируемые и трудновоспроизводимые ошибки.
npm ci: более строгая установка
Команда npm clean-install (или сокращенно npm ci
) является заменой npm install
на месте с двумя основными отличиями:
- Выполняет чистую установку: если папка
node_modules
существует, npm удаляет ее и устанавливает новую. - Он проверяет согласованность: если
package-lock.json
не существует или не соответствует содержимомуpackage.json
, npm останавливается с ошибкой.
Думайте о npm ci
как о более строгой версии npm install
, которая не допускает несоответствий любого рода (это отметило бы ошибку, которую я сделал ранее).
Пробуем npm ci в семафоре
Хорошей новостью является то, что npm ci
и npm install
взаимозаменяемы. Таким образом, вы можете с удобством использовать npm install
на своем компьютере для разработки, переключаясь на npm ci
в среде непрерывной интеграции для дополнительной безопасности.
Давайте попробуем использовать npm ci
в одной из быстрых демоверсий Semaphore. Для продолжения вам потребуется учетная запись Семафор. Вы можете зарегистрироваться бесплатно, нажав кнопку Sign up with GitHub.
После входа в систему создайте новый проект, нажав +Новый проект в правом верхнем углу. Затем выберите демонстрацию JavaScript. В качестве альтернативы вы можете разветвить демонстрационный репозиторий на GitHub.
Это позволит клонировать новый репозиторий на GitHub и настроить пример конвейера:
Теперь, когда мы знаем, что демо работает, мы изменим конвейер. Нажмите Редактировать рабочий процесс, чтобы открыть конструктор рабочего процесса:
Нажмите на блок Установить зависимости, чтобы отобразить два задания внутри.
Первое, что нужно понять, это то, что нет смысла использовать кеш семафора для сохранения node_modules
между заданиями. npm ci
всегда удаляет эту папку перед установкой.
Внесите следующие изменения в оба задания:
- Полностью удалите линии
cache restore ...
иcache store ...
. - Замените
npm install
наnpm ci
.
Повторите эти шаги в остальных блоках. Затем нажмите Запустить рабочий процесс › Начать.
Отныне, если кто-то забудет зафиксировать package-lock.json
или package.json
, конвейер поймает ошибку, прежде чем она сможет причинить какой-либо вред.
Установка против чистой установки: что лучше?
С одной стороны, npm ci
поведение безопаснее и разумнее; это может предотвратить много неприятностей в будущем. Кроме того, поскольку процесс установки прост, он работает быстрее, чем npm install
. С другой стороны, его использование означает, что мы не можем воспользоваться кешем для ускорения сборки.
Итак, что лучше? По-разному. Я могу представить себе три сценария:
Сценарий 1: вам не нужен кеш
Если вы уже не используете кеш или если его удаление почти не влияет на время сборки, выберите максимально безопасный уровень и измените каждые npm install
на npm ci
в конвейере, как мы сделали в примере.
Сценарий 2: вам абсолютно необходим кеш
Если вы не можете позволить себе вообще замедлить конвейер CI, оставьте npm install
и используйте кеш как обычно. Тем не менее, рассмотрите возможность перехода на npm ci
в конвейерах непрерывной доставки или развертывания. Например, вы можете переключиться на npm ci
в своих Dockerfiles на этапе развертывания. Таким образом, вы будете точно знать, какие модули включены в рабочую версию.
Сценарий 3: вы хотите использовать и кеш, и npm ci
Здесь вы хотели бы использовать npm ci
, но удаление кеша просто делает конвейер слишком медленным. Решение состоит в том, чтобы заменить первое появление npm install
в вашем конвейере на npm ci
и сразу же кэшировать папку node_modules
. Последующие задания будут использовать кэшированные модули, которые, как вы знаете, непротиворечивы. Этот вариант находится между двумя предыдущими сценариями и уравновешивает скорость и согласованность.
Вывод
Приветствуются любые изменения, которые спасают нас от совершения ошибки, какой бы незначительной она ни была. Я надеюсь, что этот пост поможет вам найти лучший компромисс между скоростью, удобством и надежностью для ваших проектов JavaScript.
Узнайте больше о JavaScript и Node:
Первоначально опубликовано на https://semaphoreci.com 5 ноября 2020 г.