Менее известный, чем его брат, npm install, с npm clean-install (сокращенно npm ci), ваш процесс CI/CD становится более надежным. Вот как это использовать.

Что такое нпм?

Каждый разработчик, который работал с чем-либо, связанным с Интернетом, использовал или слышал о диспетчере пакетов Node: npm. npm — это утилита командной строки, которая поставляется с Node.js. Его основная функция — установка модулей JavaScript из официального репозитория Node.

Типичный вызов установки:

$ npm install -s MODULE_NAME

Это делает ряд вещей:

  1. Ищет модуль по имени.
  2. Загружает и устанавливает модуль и его зависимости.
  3. Обновляет (или создает) package-lock.json. Этот файл называется lockfile и содержит URL-адрес и контрольную сумму каждого установленного модуля.
  4. Добавляет имя и версию модуля в 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 всегда удаляет эту папку перед установкой.

Внесите следующие изменения в оба задания:

  1. Полностью удалите линии cache restore ... и cache store ....
  2. Замените 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 г.