Русский
Русский
English
Статистика
Реклама

Semver

Установка и обновление зависимостей в JavaScript

14.07.2020 12:05:49 | Автор: admin

Установка и обновление зависимостей JavaScript


И снова привет! В прошлом посте мы начали рассматривать процесс управления зависимостями в JavaScript, разобрали основы: что такое npm-пакет; как выглядит манифест пакета; в каких полях прописываются зависимости; что такое дерево зависимостей; а также основы семантического версионирования (semver). Если вы пропустили предыдущий пост, то рекомендую начать с него.


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


npm shell autocomplete


В моих постах я буду часто упоминать npm и различные команды с его использованием. Чтобы сделать набор команд в терминале чуточку удобнее, предлагаю установить автодополнения в ваш shell.


Сделать это достаточно легко, достаточно выполнить следующие команды:


  • Для Bash:


    npm completion >> ~/.bashrcsource ~/.bashrc
    

  • Для Z shell:


    npm completion >> ~/.zshrcsource ~/.zshrc
    

    Это добавит в конфигурационный файл shell-а необходимый скрипт. После этого, если вы напишите npm smth, а затем нажмете [TAB], shell автоматически дополнит вашу команду или предложит варианты дополнения.



Инициализация проекта


Как мы уже успели обсудить, проектом в npm является любая директория, в которой находится манифест (файл package.json). Вы можете создать манифест вручную в любом редакторе кода, либо выполнить команду npm init. Она по умолчанию задаст вам серию вопросов в интерактивном режиме и сгенерирует простейший манифест в текущей директории на основе ваших ответов.


Однако я предпочитаю вызывать команду следующим образом: npm init -y, а затем править манифест в редакторе. При таком вызове npm не будет задавать вопросов, а просто сгенерирует минимальный манифест со значениями по умолчанию.


Использование инициализаторов


Говоря про npm init нельзя не упомянуть про возможность использования специальных пакетов инициализаторов (npm initializers). Они облегчают создание новых проектов, генерируя необходимый boilerplate-код.


Используются пакеты так: npm init <initializer>, где <initializer> это название инициализатора (например, esm или react-app).


Инициализатор, по сути, это специальный npm-пакет с префиксом create-, который загружается из npm registry в момент вызова команды и выполняется. У каждого инициализатора могут быть свои аргументы, настройки и поведение.


Например, можно создать React-приложение с помощью инициализатора `create-react-app: npm init react-app -- my-react-app. Два минуса позволяют разделить аргументы CLI, которые передаются в команду npm init от тех, что передаются в сам инициализатор. Особенность инициализатора React, например, в том, что проект будет создан не в текущей директории, а в поддиректории с названием, которое вы укажете при вызове (в примере: my-react-app).


Вы можете сами написать свой инициализатор и опубликовать его в registry. Это может быть особенно удобно в корпоративной среде, где существует множество стандартов и соглашений вы можете оформить их все в виде инициализатора и опубликовать его в закрытом npm registry. Таким образом, разработчики внутри компании смогут быстро создавать новые проекты.


Добавление зависимостей в проект


Как мы выяснили ранее, зависимости прописываются в манифесте проекта в полях dependencies, devDependencies, peerDependencies или optionalDependencies. Чтобы добавить новую зависимость в проект, необходимо использовать команду: npm install <package-name>, или сокращенно: npm i <package-name>.


Пример: npm i lodash.


Эта команда установит lodash самой свежей стабильной версии и добавит эту зависимость в поле dependencies манифеста проекта.


Аналогично можно устанавливать сразу несколько зависимостей одновременно: npm i lodash express passport.


Команда install с флагами также позволяет выбрать, в какое поле будет добавлена зависимость:


  • -P, --save-prod
    установка в dependencies (работает по умолчанию)
  • -D, --save-dev
    установка в devDependencies
  • -O, --save-optional
    установка в optionalDependencies

Если вам нужно установить зависимость в peerDependencies, то придётся сделать это вручную т. к. npm не предусматривает для этого специальной команды. Как вариант, можно сначала установить зависимость в dependencies при помощи команды npm install, а потом перенести ее вручную в peerDependencies. В этом случае вам не придется угадывать свежую версию пакета (если вдруг ваш IDE не поддерживает автоматическую интеграцию с npm).


Конечно, зависимость можно добавить в список и самостоятельно в редакторе кода, но я всегда рекомендую использовать команды npm для добавления зависимостей, т. к. это дает более надежный результат: вы гарантированно получаете последнюю версию нужного пакета, а список зависимостей будет корректно отсортирован в лексикографическом порядке (что позволит избежать конфликтов при мерже в Git).


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

Добавление зависимости старой версии


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


  • npm i lodash@3.9.2
  • npm i lodash@3

Однако делать это рекомендуется только в самом крайнем случае. Об этом я расскажу подробнее чуть позже.


Установка зависимостей


Выше мы рассмотрели варианты добавления зависимостей в проект. Но как установить зависимости, которые уже прописаны в манифесте, если вы, к примеру, только сделали git clone?


Для этого достаточно просто выполнить команду npm install или npm i без аргументов. Npm прочитает содержимое манифеста, найдет указанные зависимости и установит их в проект.


Существует возможность установить только одну категорию зависимостей:


  • --only=prod[uction]
  • --only=dev[elopment]

Это может быть полезно, если вы хотите, к примеру, только запустить программу на Node.js, но не работать над ней.


Просмотр установленных зависимостей


Прежде чем мы поговорим про обновление зависимостей, было бы полезно научиться просматривать их. Для этой цели в npm также предусмотрена специальная команда: npm ls.


Синтаксис команды выглядит следующим образом: npm ls [<package-name>], где <package-name> опциональное название пакета, который вы хотите найти в дереве зависимостей.


Если вызвать команду без аргументов, то она выведет полное дерево зависимостей (не ослепните, дерево может быть огромным):



Крошечная порция результата выдачи команды npm ls в большом проекте.


Иногда бывает необходимо найти ту или иную транзитивную зависимость в дереве проекта, чтобы понять, какие пакеты тянут ее, для этого можно передать название пакета в команду: npm ls lodash.



Результат поиска пакета lodash в дереве зависимостей крупного проекта при помощи команды npm ls lodash.


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


npm ls --depth=0

Как вы уже догадались, по умолчанию команда работает со всем деревом целиком.


Также вы можете использовать опции dev или prod для того, чтобы вывести только зависимости из полей dependencies или devDependencies:


  • npm ls --dev[elopment]
  • npm ls --prod[uction]

Использование же опции json позволяет получить дерево зависимостей в формате. пригодном для машинной обработки. В этом режиме npm выводит более подробную информацию о пакетах, а не только название и версию. Режим может быть также полезен для отладки и сравнения двух деревьев (например, в разных ветках проекта):


npm ls --json

Обновление зависимостей


Как мы уже рассмотрели в предыдущем посте, в npm для управления зависимостями используется система семантического версионирования (semver). Благодаря ей вы можете обновлять зависимости в своем проекте с предсказуемыми результатами: к примеру, если зависимость обновилась с версии 1.2.3 до версии 1.2.4 (patch update) или 1.3.0 (minor update), то это не сломает ваш проект, т. к. по правилам semver такие обновления не должны нарушать обратной совместимости. А если обновление производится с версии 1.2.3 до версии 2.0.0 или выше, то здесь вам следует обязательно заглянуть в журнал изменений (changelog) данного пакета, чтобы убедиться, что обновление ничего не сломает. Возможно, вам придется внести изменения в свой код, чтобы восстановить совместимость.


Несмотря на то, что semver гарантирует достаточно высокий уровень безопасности при обновлении, бывают случаи, когда разработчики какого-то пакета могут нарушать правила и вносить критические изменения в patch- или minor-обновлениях (в первую очередь это касается непопулярных пакетов, которыми управляют менее опытные разработчики).

В моей практике такое случается очень редко, но никто не застрахован от ошибок. Поэтому после любого обновления зависимостей в проекте необходимо обязательно проводить полный цикл сборочных и тестовых мероприятий, чтобы убедиться, что всё работает как нужно, особенно перед выкаткой в эксплуатацию (надеюсь, это происходит у вас автоматически).

Версии зависимостей


Давайте теперь рассмотрим, как именно прописываются версии зависимостей в манифесте проекта и какие механизмы дает нам semver для управления процессом обновления. Как я уже упомянул выше, при установке зависимости npm автоматически устанавливает самую свежую версию и включает наиболее свободный режим обновления для этой зависимости: разрешает как patch-, так и minor-обновления.


В package.json это выглядит следующим образом:


{  "dependencies": {    "lodash": "^4.17.17"  }}

Символ ^ (caret, hat или крышечка) указывается перед номером версии и имеет особое значение в semver. В данном случае это означает, что версия зависимости lodash должна обновляться до максимально доступной, но не выше 5.0.0, т. е. разрешает patch- и minor-обновления, но запрещает обновления, нарушающие обратную совместимость.


Помимо крышечки существует еще довольно много способов задать диапазон версии, все их можно посмотреть в описании пакета semver, который и реализует эту функциональность в npm. Однако в 99 % случаев зависимости стоит указывать именно через крышечку (как делает npm по умолчанию), т. к. это дает наибольшую гибкость при обновлении зависимостей, и в то же время защищает вас от критических изменений.


Фиксация версий зависимостей


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


{  "dependencies": {    "lodash": "4.17.17"  }}

Это гарантирует, что lodash будет установлен именно версии 4.17.17, ни больше, ни меньше.


Однако фиксация версий зависимостей имеет ряд существенных недостатков:


  • такие зависимости перестают обновляться, вы перестанете автоматически получать исправления багов, а самое главное улучшения безопасности,
  • если в дереве зависимостей вашего проекта этот пакет встречается более одного раза, то это приведет к тому, что он будет установлен несколько раз (несколько разных версий). Про дублирование зависимостей мы подробнее поговорим в будущем, но пока поверьте мне на слово, это может приводить к существенным сложностям.

По этим причинам использовать фиксацию зависимостей нужно в очень редких исключительных случаях и только в качестве временной меры.


Просмотр устаревших зависимостей


Новые версии пакетов выходят регулярно. Поэтому пакеты, установленные в вашем проекте, могут устаревать и их необходимо регулярно обновлять. Команда npm outdated позволяет вывести список устаревших пакетов.



Результат команды npm outdated в проекте, где установлены две устаревшие зависимости.


Эта команды выводит таблицу со следующими колонками:


Колонка Описание
Package Название пакета
Current Текущая установленная версия
Wanted Максимальная версия, которая удовлетворяет диапазону semver, прописанному в манифесте проекта
Latest Версия пакета, которую автор указал в качестве самой свежей (как правило, максимально доступная версия пакета)
Location Место расположения зависимости в дереве

По умолчанию команда npm outdated выводит список прямых зависимостей вашего пакета. Однако, если использовать аргумент depth с указанием глубины просмотра, то npm покажет устаревшие зависимости, в том числе и на заданной глубине дерева зависимости:


npm outdated --depth=10

Чтобы просмотреть полный список всех устаревших зависимостей, можно использовать следующую команду:


npm outdated --depth=9999

Обновление устаревших зависимостей


Обновить устаревшие зависимости в npm можно при помощи команды npm update.


Команда проверяет версии установленных зависимостей по отношению к версиям, доступным в npm registry, с учётом диапазонов версий semver, указанных в манифесте вашего проекта. Если установленная версия того или иного пакета отличается от максимальной версии, доступной в registry (учитывая ограничение semver), то более свежая версия будет загружена и установлена, а манифест будет обновлен, чтобы минимальная версия в диапазоне соответствовала установленной. Важно заметить, что весь этот процесс протекает без нарушения semver, т. е. вызов npm update никогда не приведет к нарушению диапазонов версий, указанных в вашем манифесте.


Приведем пример: допустим, в вашем проекте указан диапазон версий пакета lodash: ^4.16.4. Вызов npm update приведет к тому, что пакет будет обновлен до версии 4.17.19, а манифест будет автоматически изменен, чтобы содержать диапазон ^4.17.19.


По аналогии с командой npm outdated, команда npm update также поддерживает аргумент depth и по умолчанию обновляет только прямые зависимости проекта, не трогая зависимости в глубине дерева. Поэтому, чтобы обновить все зависимости в проекте необходимо вызывать команду следующим образом:


npm update --depth=9999

Это гарантирует. что абсолютно все зависимости в дереве (включая транзитивные) будут обновлены до самых последних версий. Такую процедуру рекомендуется делать регулярно, чтобы в вашем проекте всегда был самый свежий код с последними исправлениями багов и проблем безопасности.


npm-check


В качестве альтернативы командам npm outdated и npm update хочу предложить интересный инструмент под названием npm-check.


Вы можете установить его при помощи следующей команды:


npm i -g npm-check

Инструмент поддерживает интерактивный режим, позволяя вам выбрать галочками те пакеты, которые вы хотите обновить. Кроме того, он позволяет обновлять пакеты не только в рамках ограничений semver, но и игнорируя их, перепрыгивая с одной мажорной версии зависимости на другую. Разумеется, делать это нужно осмысленно: тщательно изучая журнал изменений каждого мажорно-обновляемого пакета на наличие нарушений обратной совместимости. И никогда не забывайте тестировать ваш код после любых обновлений!



Результат вызова npm-check в проекте: доступно два обновления, одно мажорное и одно минорное.



Также результат вызова npm-check, но уже в интерактивном режиме: галочками можно выбрать зависимости, которые вы хотите обновить.


В качестве очень полезного бонуса: npm-check позволяет обнаружить, если какая-то из зависимостей не используется в проекте:



npm-check сообщает о том, что пакет lodash, возможно, не используется в проекте.


Рекомендую всегда держать этот незаменимый инструмент (или аналогичный) в своем арсенале.


Удаление зависимостей


Ну и наконец давайте рассмотрим, как мы можем удалять ранее добавленные зависимости в проект. Для этого существует команда npm uninstall, или сокращенно rm, r или un. Чтобы удалить один или несколько проектов, мы можем вызвать команду следующим образом:


npm rm lodash express

Будут удалены указанные пакеты как из файловой системы проекта, так и из его манифеста.


Workflow работы с npm-проектом


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


  • Инициализация проекта:
    • npm init
      (интерактивно)
    • npm init -y
      (с последующим редактированием в IDE)
  • Добавление зависимостей:
    • npm install <dependency>
    • npm install <dependency-1> <dependency-2>
    • npm install -D <dev-dependency>
  • Проверка и обновление зависимостей:
    • npm outdated
      (просмотр прямых устаревших зависимостей)
    • npm outdated --depth=9999
      (просмотр всех устаревших зависимостей)
    • npm update
      (обновление прямых устаревших зависимостей с учетом semver)
    • npm update --depth=9999
      (обновление всех устаревших зависимостей с учетом semver)
    • npm-check
      (просмотр прямых устаревших зависимостей)
    • npm-check -u
      (интерактивное обновление прямых устаревших зависимостей)
  • Удаление зависимостей:
    • npm rm <dependency>

Продолжение следует


Мы подробнее рассмотрели процесс инициализации проекта, добавления, установки и обновления зависимостей. Рассмотрели, как semver работает на практике при обновлении зависимостей.


В следующей статье мы детально поговорим про такую важную концепцию, как lock-файлы npm (shrinkwrap), и научимся пользоваться этими механизмами для повышения стабильности при работе с зависимостями. Увидимся через неделю, а пока подписывайтесь и следите за обновлениями!

Подробнее..

Lock-файлы npm

30.07.2020 14:08:36 | Автор: admin

Lock-файлы npm


Всем привет! В прошлом посте мы рассмотрели экосистему npm в качестве источника хаоса в нашем проекте, и научились с умом подходить к выбору зависимостей, чтобы минимизировать наши риски. Сегодня мы пойдем дальше и рассмотрим lock-файлы npm, которые помогают повысить стабильность проекта в процессе работы над ним.


Когда манифеста мало


Как мы уже определили, npm берёт на входе манифест проекта (файл package.json) и описанные в нем зависимости, а на выходе мы получаем локально сгенерированную директорию node_modules, в которой содержатся все установленные зависимости.


Не будем сейчас касаться деталей структуры node_modules, однако замечу, что установленные пакеты не всегда имеют плоскую структуру, а могут в отдельных случаях формировать некую иерархию, когда один или несколько пакетов вложены в другой пакет. Как правило, это происходит, когда в дереве зависимостей присутствует один и тот же пакет, но разных версий. При этом сама структура в файловой системе не детерминирована на 100 %, и может зависеть, в том числе, от порядка установки зависимостей.


Также разные версии npm могут иметь различные алгоритмы установки зависимостей, и структура файлов будет отличаться.


Вы помните, что список зависимостей в манифесте проекта содержит диапазон версий semver, что позволяет обновлять зависимости? Получается, что установка зависимостей в разное время будет приводить к разным результатам, потому что содержимое npm registry постоянно меняется, регулярно выходят новые пакеты. Кроме того, поскольку мы имеем дело с деревом зависимостей, то транзитивные зависимости (зависимости ваших зависимостей) тоже будут меняться во времени.


А еще может возникнуть ситуация, когда содержимое пакета, лежащего в npm registry, было заменено без обновления версии. Это называется мутацией пакетов и запрещено в официальном npm registry, однако в частных реестрах вполне возможно. А что, если содержимое пакета было заменено злоумышленником, чтобы внести в код какие-то закладки?


Учитывая все эти факторы, становится очевидно, что структура данных в директории node_modules очень нестабильна и может меняться во времени, даже если ваш манифест при этом остается нетронутым.


Наивно было бы попытаться зафиксировать зависимости, прописывая строгие версии в манифесте проекта (вместо диапазонов semver): как мы рассмотрели выше, это не даст существенных результатов, потому что транзитивные зависимости всё равно будут обновляться. Да и другие факторы не перестанут влиять на установку зависимостей. Кроме того, если вы заморозите ваши прямые зависимости, то получится ситуация, когда более старые версии будут работать с более свежими версиями транзитивных зависимостей, и потенциально это повышает вероятность проблем с интеграцией.


А теперь представьте, что у нас в проекте есть конвейер CI/CD и специальный сервер, который собирает, тестирует и выкатывает приложения в разные среды выполнения. Как правило, такие решения привязываются к ID коммита в Git (или к Git-тегам), и на каждый коммит система генерирует готовый к выкатке артефакт (архив с готовыми для выполнения файлами). Таким образом, на вход конвейера поступает код из Git-репозитория, версионированный через ID коммита, а на выходе вы получаете протестированный и готовый к выкатке артефакт. В идеале, это должно работать как чистая функция (pure function): если вы пересоберёте коммит, созданный несколько месяцев назад, то должны получить на выходе тот же самый артефакт. Однако мы не можем хранить содержимое node_modules в Git, и получается, что после клонирования репозитория нам необходимо вызывать установку зависимостей из реестра npm. А, как мы уже выяснили, этот процесс довольно нестабилен и привязан к глобальному состоянию экосистемы (содержимому npm registry, версиям npm и т. д.). Получается, что npm вносит хаос в наш конвейер CI/CD и мы уже не можем получить одинаковую сборку по ID коммита.


Lock-файлы приходят на помощь


Чтобы предотвратить все описанные выше проблемы и сделать использование зависимостей гораздо более стабильным, npm (как и любой другой современный менеджер) предлагает специальный механизм заморозки зависимостей. Работает это автоматически и прямо из коробки: впервые вызывая команду npm install, npm не только устанавливает все зависимости и создаёт директорию node_modules, он также создает специальный файл package-lock.json. Этот файл называется lock-файлом и содержит в себе полную информацию обо всех установленных зависимостях, включая их точные версии, URL npm registry, из которого был скачан пакет, а также SHA-хэш самого архива с пакетом. Помимо прочего, lock-файл npm описывает еще и порядок установки зависимостей, и их вложенность в файловой системе.


При повторном вызове команды npm install менеджер пакетов увидит, что lock-файл содержится в директории проекта, и в дальнейшем зависимости будут устанавливаться в полном соответствии с информацией из lock-файла. Таким образом, вы можете вызывать команду npm install сколько угодно раз на любой машине и в любое время (даже спустя месяцы), и на выходе будете получать одинаковую структуру файлов в директории node_modules. Также стоит заметить, что установка зависимостей через lock-файл осуществляется быстрее, потому что npm не нужно сверять диапазоны зависимостей из манифеста с данными, доступными в реестре npm. Даже если версия npm обновится и алгоритм установки зависимостей поменяется, lock-файл всё равно будет гарантировать точный и стабильный результат, потому что файл, по сути, описывает результат работы алгоритма на основе входных данных. В каком-то смысле эта концепция аналогична кэшированию.


Чтобы использовать преимущества lock-файла, его необходимо добавить в систему контроля версий. Таким образом, вы строго привяжете полное дерево зависимостей к коммитам в Git. Это будет гарантировать стабильное воспроизводство сборок в вашей системе CI/CD и позволит надежно путешествовать во времени.


Кроме того, каждый разработчик, который склонирует Git-репозиторий к себе на машину, получит точно такое же дерево зависимостей, как и у вас. Это устранит известную проблему из разряда странно, а у меня всё работает (it works on my machine).


it worked on my machine


Структура package-lock.json


Npm генерирует lock-файл полностью автоматически на основе данных из манифеста проекта, глобального состояния npm registry и алгоритма установки зависимостей npm. Однако содержимое файла вполне читаемо человеком и может быть использовано даже на этапе code review. Diff lock-файла покажет, какие зависимости в дереве были обновлены, какие были удалены, а какие добавлены. Наверное, нет смысла изучать изменения этого файла при каждом обновлении, но при обнаружении каких-то деградаций это может сильно помочь в поиске виновного пакета и сэкономить вам кучу времени. Но чтобы это работал эффективнее и размер изменений был минимальным, я рекомендую обновлять зависимости как можно чаще (гораздо проще выявить проблему, если у вас обновилось три пакета в дереве зависимостей, а не сотня).


Давайте теперь рассмотрим содержимое файла package-lock.json в тестовом проекте, где установлена только одна зависимость express.


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

package-lock.json


{  "name": "test",  "version": "1.0.0",  "lockfileVersion": 1,  "requires": true,  "dependencies": {    "express": {      "version": "4.17.1",      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",      "integrity": "sha512-mHJ9O79RqluphRr7xlEMXTnYt4g==",      "requires": {        "debug": "2.6.9",        "send": "0.17.1"      }    },    "debug": {      "version": "2.6.9",      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",      "integrity": "sha512-bC7ElrdJaJnPbAPeAPVMNcKGsHMA==",      "requires": {        "ms": "2.0.0"      }    },    "ms": {      "version": "2.0.0",      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="    },    "send": {      "version": "0.17.1",      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",      "integrity": "sha512-BsVKsiGcQMFwT8UcNuE3V4fT9sAg==",      "requires": {        "debug": "2.6.9",        "depd": "~1.1.2",        "destroy": "~1.0.4",        "encodeurl": "~1.0.2",        "escape-html": "~1.0.3",        "etag": "~1.8.1",        "fresh": "0.5.2",        "http-errors": "~1.7.2",        "mime": "1.6.0",        "ms": "2.1.1",        "on-finished": "~2.3.0",        "range-parser": "~1.2.1",        "statuses": "~1.5.0"      },      "dependencies": {        "ms": {          "version": "2.1.1",          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",          "integrity": "sha512-tgp+dl5cGk28utYYaD/kOWhYQvyg=="        }      }    }  }}

Итак, давайте разбираться. Начнем с основных корневых свойств:


  • name и version тут всё просто, это название и версия проекта из его манифеста на момент создания lock-файла.
  • lockfileVersion это версия формата, в котором представлен lock-файл. Она нужна для расширяемости, если разработчики npm в будущем придумают какой-то новый формат хранения.
  • dependencies полное плоское дерево зависимостей вашего проекта; объект, в котором ключ это название пакета, а значение дескриптор.

Дескриптор каждой отдельно взятой зависимости выглядит следующим образом:


  • version точная версия пакета на момент установки.
  • resolved URL пакета в реестре npm, откуда он был скачан.
  • integrity SHA-хэш пакета; проверочная сумма, которая позволяет убедиться, что в пакет не было внесено изменений как в процессе скачивания, так и на стороне хранилища (защита от мутации). Это очень важный элемент безопасности при работе с npm, который гарантирует, что злоумышленник не сможет как-то вмешаться в код пакета. При обнаружении несоответствия вызов npm install будет прерван с ошибкой.
  • requires объект, описывающий транзитивные зависимости (копируется из поля dependencies манифеста стороннего пакета). Ключ является названием пакета, а значение диапазоном версий semver.
  • dependencies аналогично полю dependencies, описанному выше. Позволяет рекурсивно описывать структуру вложенных пакетов, когда в дереве зависимостей содержится один и тот же пакет, но разных версий.
  • dev если true, то ээта зависимость является только зависимостью для разработки (необходимо для раздельной установки зависимостей).

Дублирующиеся зависимости


Обратите внимание, что в примере выше пакет express (наша прямая зависимость) зависит от пакета debug, а тот, в свою очередь, от ms@2.0.0. В то же время, пакет send также зависит от ms, но уже версии 2.1.1. Получается, что в директории node_modules пакет ms должен быть установлен два раза (разных версий), но, в силу сложившихся правил в Node.js, два пакета разных версий не могут быть установлены в корне. По этой причине одна версия устанавливается в корень (ms@2.0.0), а вторая в поддиректорию пакета send (ms@2.1.1). Это решение как раз и отражено в lock-файле. В том числе благодаря этому достигается стабильность директории node_modules.


Подробнее про дублирование зависимостей мы поговорим в будущих статьях.


Ручные правки


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


Конфликт в lock-файлах npm


Если несколько разработчиков трудятся в одной ветке и используют lock-файлы, то в какой-то момент может возникнуть merge-конфликт в Git. В этом случае достаточно просто устранить конфликты в файле манифеста (если они есть), а потом выполнить npm install: менеджер пакетов автоматически исправит конфликты в lock-файле.


Если вам не хочется править конфликты в lock-файлах вручную, то можете установить специальный merge-драйвер для Git, который умеет работать с npm. Это позволит исправлять конфликты в файле package-lock.json автоматически. Однако, если конфликт возникнет в манифесте, то вам всё равно будет необходимо исправить конфликт вручную, а потом вызвать npm install.


Установить merge-драйвер для npm можно следующим образом:


npx npm-merge-driver install -g

При возникновении конфликта при вызове команд Git в консоли будет выведено:


npm WARN conflict A git conflict was detected in package-lock.json. Attempting to auto-resolve.Auto-merging package-lock.json

Обновление lock-файла


Для работы с lock-файлами не требуется каких-то особых действий со стороны разработчика, npm автоматически обновляет lock-файл, когда в этом есть необходимость. Например, если вы вызываете команду npm install lodash, то помимо того, что npm добавит новую зависимость в манифест и установит её, он автоматически обновит lock-файл. Таким образом, npm всегда следит, чтобы lock-файл был в актуальном состоянии.


Однако если вы вносите изменения в манифест проекта вручную, например, добавляя новую зависимость, то возникает дрифт (рассинхронизация) между манифестом и lock-файлом. Менеджер пакетов достаточно умён, чтобы обнаружить этот дрифт: когда вы вызовете команду npm install, npm проанализирует актуальность lock-файла, и если он устарел, то менеджер пакетов автоматически обновит lock-файл, используя данные из манифеста.


Установка зависимостей в CI/CD


Как я сказал выше, если npm обнаружит отставание lock-файла от манифеста, то это приведет к обновлению lock-файла и установке зависимостей из манифеста. Такое поведение очень удобно при разработке, но совершенно непригодно, и даже опасно в контексте конвейера CI/CD, потому что может привести к неожиданным результатам из-за слетевшей блокировки зависимостей.


Чтобы этого не происходило, разработчики npm добавили специальную команду npm ci. В отличие от npm install, эта команда никогда не обновляет lock-файл. Более того, если в проекте отсутствует или устарел lock-файл, то npm ci вернет код ошибки и прервет выполнение конвейера, гарантируя, что ничего плохого не случится (принцип Fail-fast). Кроме того, npm ci полностью удаляет директорию node_modules перед установкой зависимостей, что гарантирует установку на чистый лист.


По этой причине никогда не следует использовать команду npm install в рамках конвейера CI/CD, обязательно используйте npm ci вместо нее. Идите и проверьте это прямо сейчас! (я подожду).


Разные типы проектов


Давайте теперь поговорим про особенности использования lock-файлов в проектах разных типов. Первое, о чём стоит сказать: файл package-lock.json не публикуется в npm registry. Это означает, что если вы публикуете свой пакет в реестр npm (библиотека), то содержимое вашего lock-файла не будет оказывать влияния на дерево зависимостей при установке вашего пакета в чей-то проект. В этом случае играет роль только содержимое вашего манифеста. Это и хорошо: если бы каждая библиотека замораживала свои зависимости, то дерево зависимостей в конечном проекте было бы ещё больше (куда уж больше?) и содержало огромное количество дублей. Адекватно управлять зависимостями стало бы невозможно.


Shrinkwrap


Однако в npm есть специальная команда [npm shrinkwrap](http://personeltest.ru/aways/docs.npmjs.com/cli/shrinkwrap). Она создает файл npm-shrinkwrap.json в корне проекта, который является тем же lock-файлом, только с другим именем и семантикой. Особенность его в том, что, в отличие от package-lock.json, он таки публикуется в реестр npm и оказывает непосредственное влияние на дерево зависимостей при установке вашего пакета. Фактически, он замораживает дерево зависимостей вашего пакета, даже если тот устанавливается в другой проект.


Как я сказал выше, использовать это решение для библиотек очень вредно, поэтому не стоит этого делать. Однако, оно может быть полезно, если вы разрабатываете программу на Node.js, которая должна выполняться на компьютере пользователя (например, аналог webpack, gulp, create-react-app и т. д.). Если программа устанавливается преимущественно глобально на компьютере пользователя (npm i -g), то использование shrinkwrap-файла гарантирует, что на машине пользователя программа будет иметь те же зависимости, что и на вашей машине. Так что, если у вас есть явные причины опасаться дрифта зависимостей в вашей программе, то вы можете воспользоваться npm shrinkwrap. В остальных случаях я не рекомендовал бы использовать эту команду.


Кстати, файл npm-shrinkwrap.json имеет приоритет над файлом package-lock.json. В проекте достаточно только одного файла.


Тестирование пакета-библиотеки


Важным этапом в работе над проектом библиотеки является его тестирование. Дело в том, что вы полностью не контролируете внешние условия, в которых будет вызываться ваша библиотека. Даже если вы зафиксируете прямые зависимости, вы не сможете зафиксировать транзитивные (разве что будете использовать shrinkwrap, но мы уже выяснили, что это плохая практика).


По этой причине вы не можете гарантировать среду, однако можете протестировать работу библиотеки со свежими версиями всех зависимостей, чтобы убедиться, что в новых и свежих проектах ничего не сломается. Однако, если вы будете использовать lock-файл в вашем проекте, то он будет постоянно оттягивать версии ваших зависимостей назад, не давая вам полноценно тестировать совместимость с самыми свежими версиями всех зависимостей (которые наверняка будут у ваших пользователей). Либо вам придется постоянно вызывать глубокий npm update перед каждым тестом.


Руководствуясь этим, некоторые разработчики советует вообще не использовать lock-файлы для проектов библиотек. Однако, я считаю это слишком радикальным советом. Дело в том, что помимо продуктовых runtime-зависимостей вашего пакета существуют еще и dev-зависимости. Если вы откажетесь от lock-файла, то ваши dev-зависимости выйдут из-под контроля, то есть вы потеряете некий островок стабильности.


Более подходящим решением, на мой взгляд, была бы реорганизация конвейера CI/CD таким образом, чтобы код библиотеки тестировался в проекте без использования lock-файла, путем установки самых свежих доступных зависимостей. Собирать же пакет (если требуется) можно и с участием lock-файла (для этого можно использовать два разных этапа в вашем CI/CD конвейере).


С глаз долой


У многих разработчиков lock-файлы вызывают чувство раздражения, видимо, из-за непонимания их предназначения или особенностей их работы. Такие разработчики норовят добавить package-lock.json в .gitignore или вообще настроить npm, чтобы запретить генерирование lock-файлов. При этом они (часто сами того не понимая) жертвуют стабильностью и безопасностью своего приложения, а также теряют достаточно мощный инструмент отслеживания изменений зависимостей в проекте. Часто эти же люди начинают строго прописывать версии зависимостей в основном манифесте, чтобы как-то компенсировать эту нестабильность, не отдавая себе отчета в том, что это не решает проблему, а только создает иллюзию её решения. Я уже не говорю о том, что они используют инструмент не по назначению, теряя гибкость разработки, которая обеспечивается, в том числе, и механизмами семантического версионирования.


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


Обновляйтесь чаще!


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


Залог высокого качества, поддерживаемости и удобства разработки вашего проекта заключается в частом обновлении зависимостей. Согласитесь, гораздо проще обновлять зависимости маленькими порциями по мере их выхода. Diff lock-файла всегда покажет, что именно обновилось. Если он будет небольшим, то вы легко сможете его прочитать. Если же после обновления возникнет проблема, то, скорее всего, она будет одна, и её будет несложно обнаружить и изолировать.


С другой стороны, если вы не будете полгода обновлять зависимости, то потом сделать это будет очень сложно, потому что крупное обновление очень сложно разделить на маленькие части. Вы получите огромный ворох изменений разной степени тяжести, а в случае возникновения проблемы (а она, скорее всего, придет не одна) выявить точное место будет гораздо сложнее (diff здесь уже будет бесполезен в силу его гигантского размера).


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


Продолжение следует


Мы рассмотрели столь важный инструмент повышения надежности и безопасности управления зависимостями, как lock-файлы npm. Использование этого механизма позволит вам существенно повысить стабильность работы вашего проекта и улучшить качество и удобство разработки.


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

Подробнее..

Основные команды bash, git, npm и yarn, а также немного о package.json и semver

05.10.2020 14:22:06 | Автор: admin
Доброго времени суток, друзья!

Предлагаю вашему вниманию небольшую шпаргалку по основным командам bash, git, npm, yarn, package.json и semver.

Условные обозначения: [dir-name] означает название директории, | означает или.

Рекомендую вводить каждую команду в терминале и внимательно изучать вывод, так вы быстро их запомните и определите, какие команды вам нужны, а какие нет.

Без дальнейших предисловий.

Оглавление:


bash


bash представляет собой инструмент командной строки, позволяющий выполнять некоторые распространенные действия.

Установка: в моем случае bash был установлен вместе с git.

Справка:

help

История команд:

history

Очистка терминала:

clear

Выход из терминала:

exit

Создание директории:

// make directorymkdir [dir-name]// примерmkdir my-app// несколько диреторийmkdir -p {dir1,dir2}// несколько вдложенных директорийmkdir -p my-app/{css,js}

Смена директории:

// change directorycd [dir-name]// примерcd my-app// сразу после созданияcd !$// родительская директорияcd ..// на два уровня вышеcd ../..// предыдущая директорияcd -// домашняя директорияcd ~

Путь к текущей директории:

// print work directorypwd

Список файлов:

// listls// включая скрытые файлыls -a | -f// больше информации// например, права доступаls -l

Создание файла:

touch [file-name]// примерtouch index.html// несколько файловtouch my-app/{index.html,css/style.css,js/script.js}

Содержимое файла:

cat [file-name]// примерcat index.html// сортировка и выборка уникальных значенийcat [file-name] | sort | uniq// меньше контентаless [file-name] // q - exit// n строк с начала файлаhead -50 [file-name]// n строк с конца файлаtail -50 [file-name]// поиск словаgrep [string] [file-name]// распаковка и просмотр содержимого архиваunzip [achive-name]// тип файлаfile [file-name]

Копирование, перемещение и удаление файла:

// copycp [file1] [file2]// movemv [file1] [file2]// пример// перемещение всех файлов из одной директории в другуюmv [dir1]/*.* [dir2]// removerm [file-name]// удаление пустой директорииrmdir [dir-name]// удаление непустой директорииrm -r [dir-name]// илиrm -rf [dir-name]

Вывод в терминал строки:

echo [string]// примерecho hello// создание или перезапись файлаecho hello > greet.txt// добавление строки в файлecho hello >> greet.txt

Загрузка файла:

wget [url]

Коннекторы:

true && echo hellofalse || echo helloecho hello ; ls

Конвейер:

// количество переносов строки - \ncat [file] | wc -l

git


git представляет собой распределенную систему контроля версий, позволяющую контролировать процесс внесения изменений в проект.

Книга Pro Git.

Скринкаст Ильи Кантора.

Быстрый старт: Git How To.

Установка: git-scm.com.

Проверка установки:

git --version

Справка:

git helpgit help [command-name]git [command-name] --help | -h

Минимальные настройки:

// --local - настройки для текущего репо// --global - настройки для текущего пользователя// --system - настройки для всей системы, т.е. для всех пользователейgit config --global user.name "My Name"git config --global user.email "myemail@example.com"

Дополнительные настройки:

// список глобальных настроекgit config --list | -l --global// редактирование глобальных настроекgit config --global --edit | -e

Создание репозитория:

git init

Очистка репозитория:

// -d - включая директории, -x - включая игнорируемые файлы, -f - принудительнаяgit clean | -dxf

Удаление файлов и директорий:

// removegit rm [file-name]git rm -r [dir-name]git rm --force | -f

Перемещение файлов:

// git add + git remove// movegit mv [old-file] [new-file]

Просмотр состояния репозитория:

git status

Добавление изменений:

git add [file-name]git add --force | -f// все файлыgit add . | --all | -A// для добавления пустой директории можно создать в ней пустой файл .gitkeep

Добавление сообщения (коммита):

// редактирование коммитаgit commit// коммит для одного изменения, если не выполнялось git add . | -A// если выполнялось, сообщение будет добавлено для всех измененийgit commit --message | -m "My Message"// для всех изменений, если git add [file-name] выполнялось несколько разgit commit --all | -a -m | -am "My Message"// исправление коммитаgit commit --amend "My Message" | --no-edit

Просмотр коммита:

// последний коммитgit show// другой коммитgit show [hash] // минимум первые 4 символа// поиск изменений по сообщению или части сообщенияgit show :/[string]// поиск коммита по тегуgit show [tag-name]

Просмотр разницы между коммитами:

git diff HEAD | @ // HEAD - как правило, текущая ветка; @ - алиас для HEAD// stagedgit diff --staged | --cachedgit diff [hash1] [hash2]// разница между веткамиgit diff [branch1]...[branch2]// просмотр разницы между коммитами при редактировании сообщенияgit commit --verbose | -v// кастомизация выводимого сообщенияgit diff --word-diff | --color-words

Просмотр истории изменений:

git log// n - количество измененийgit log -n// --since, --after - после// --until, --before - до// разницаgit log -p// быстрое форматированиеgit log --graph --oneline --stat// кастомное форматированиеgit log --pretty=format// примерgit log --pretty=format:'%C(red)%h %C(green)%cd %C(reset)| %C(blue)%s%d %C(yellow)[%an]' --date=short | format-local:'%F %R'// поиск изменений по слову, файлу, ветке; i - без учета регистраgit log --grep | -G [string] | [file] | [branch] & -i// поиск по нескольким строкамgit log --grep [string1] --grep [string2] --all-match// поиск в определенном блоке файлаgit log -L '/<head>/','/<\/head>/':index.html// поиск по авторуgit log --author=[name]

Отмена изменений:

git reset// --hard - включая рабочую директорию и индекс// --soft - без рабочей директории и индекса// --mixed - по умолчанию: без рабочей директории, но с индексомgit reset --hard [hash] | @~ // @~ - последний коммит в HEAD// аналогичноgit reset --hard ORIG_HEAD// не путать с переключением веткиgit checkoutgit restore

Работа с ветками:

// список ветокgit branch// создание веткиgit branch [branch-name]// переключение на веткуgit checkout [branch-name]// branch + checkoutgit checkout -b [branch-name]// переименованиеgit branch -m [old-branch] [new-branch]// удаление веткиgit branch -d [branch-name]// слияние ветокgit merge [branch-name]

Разрешение конфликтов при слиянии:

// обычно, при возникновении конфликта, открывается редактор// принять изменения из сливаемой веткиgit checkout --ours// принять изменения из текущей веткиgit checkout --theirs// отмена слиянияgit reset --mergegit merge --abort// получение дополнительной информацииgit checkout --conflict=diff3 --merge [file-name]// продолжить слияниеgit merge --continue

Удаленный репозиторий:

// клонированиеgit clone [url] & [dir]// просмотрgit remotegit remote showgit remote add [shortname] [url]git remote rename [old-name] [new-name]// получение изменений// git fetch + git mergegit pull// отправка измененийgit push

Теги:

// просмотрgit tag// легковесная меткаgit tag [tag-name]//примерgit tag v1-beta// аннотированная меткаgit tag -a v1 -m "My Version 1"// удалениеgit tag -d [tag-name]

Отладка

git bisectgit blamegit grep

Сохранение незакоммиченных изменений:

// сохранениеgit stash// извлечениеgit stash pop

Копирование коммита:

git cherry-pick | -x [hash]// если возник конфликт// отменаgit cherry-pick --abort// продолжитьgit cherry-pick --continuegit cherry-pick --no-commit | -n// --cherry = --cherry-mark --left-right --no-mergesgit log --oneline --cherry [branch1] [branch2]

Перебазирование:

git rebase [branch]// при возникновении конфликта// отменаgit rebase --abort// пропуститьgit rebase --skip// продолжитьgit rebase --continue// предпочтение коммитов слиянияgit rebase --preserve-merges | -p// интерактивное перебазированиеgit rebase -i [branch]

Автозавершение повторных конфликтов:

// rerere - reuse recorder resolution// rerere.enabled true | false// rerere.autoUpdate true | false// rerere-train.sh - скрипт для обучения rereregit rerere forget [file-name]

Обратные коммиты:

git revert @ | [hash]// отмена слияния// git reset --hard @~ не сработаетgit revert [hash] -m 1// git merge [branch] не сработает// отмена отменыgit revert [hash]// повторное слияние с rebasegit rebase [branch1] [branch2] | --onto [branch1] [hash] [branch2]git merge [branch]git rebase [hash] --no-ff

Пример алиасов (сокращений) для .gitconfig:

[alias]    aa = add -A    co = checkout    ci = commit -m    st = status    br = branch

Пример .gitconfig:
[user]name = [My Name]email = [myemail@example.com]username = [myusername][core]editor = [myeditor]whitespace = fix,-indent-with-non-tab,trailing-space,cr-at-eolpager = delta[web]browser = google-chrome[instaweb]httpd = apache2 -f[rerere]enabled = 1autoupdate = 1[push]default = matching[color]ui = auto[color "branch"]current = yellow boldlocal = green boldremote = cyan bold[color "diff"]meta = yellow boldfrag = magenta boldold = red boldnew = green boldwhitespace = red reverse[color "status"]added = green boldchanged = yellow bolduntracked = red bold[difftool]prompt = false[delta]features = line-numbers decorationsline-numbers = true[delta "decorations"]minus-style = red bold normalplus-style = green bold normalminus-emph-style = white bold redminus-non-emph-style = red bold normalplus-emph-style = white bold greenplus-non-emph-style = green bold normalfile-style = yellow bold nonefile-decoration-style = yellow boxhunk-header-style = magenta boldhunk-header-decoration-style = magenta boxminus-empty-line-marker-style = normal normalplus-empty-line-marker-style = normal normalline-numbers-right-format = "{np:^4} "[github]user = [username]token = token[gitflow "prefix"]versiontag = v[sequence]editor = interactive-rebase-tool[alias]a = add --allai = add -i###ap = applyas = apply --statac = apply --check###ama = am --abortamr = am --resolvedams = am --skip###b = branchba = branch -abd = branch -dbdd = branch -Dbr = branch -rbc = rev-parse --abbrev-ref HEADbu = !git rev-parse --abbrev-ref --symbolic-full-name "@{u}"bs = !git-branch-status###c = commitca = commit -acm = commit -mcam = commit -amcem = commit --allow-empty -mcd = commit --amendcad = commit -a --amendced = commit --allow-empty --amend###cl = clonecld = clone --depth 1clg = !sh -c 'git clone git://github.com/$1 $(basename $1)' -clgp = !sh -c 'git clone git@github.com:$1 $(basename $1)' -clgu = !sh -c 'git clone git@github.com:$(git config --get user.username)/$1 $1' -###cp = cherry-pickcpa = cherry-pick --abortcpc = cherry-pick --continue###d = diffdp = diff --patiencedc = diff --cacheddk = diff --checkdck = diff --cached --checkdt = difftooldct = difftool --cached###f = fetchfo = fetch originfu = fetch upstream###fp = format-patch###fk = fsck###g = grep -p###l = log --onelinelg = log --oneline --graph --decorate###ls = ls-fileslsf = !git ls-files | grep -i###m = mergema = merge --abortmc = merge --continuems = merge --skip###o = checkoutom = checkout masterob = checkout -bopr = !sh -c 'git fo pull/$1/head:pr-$1 && git o pr-$1'###pr = prune -v###ps = pushpsf = push -fpsu = push -upst = push --tags###pso = push originpsao = push --all originpsfo = push -f originpsuo = push -u origin###psom = push origin masterpsaom = push --all origin masterpsfom = push -f origin masterpsuom = push -u origin masterpsoc = !git push origin $(git bc)psaoc = !git push --all origin $(git bc)psfoc = !git push -f origin $(git bc)psuoc = !git push -u origin $(git bc)psdc = !git push origin :$(git bc)###pl = pullpb = pull --rebase###plo = pull originpbo = pull --rebase originplom = pull origin masterploc = !git pull origin $(git bc)pbom = pull --rebase origin masterpboc = !git pull --rebase origin $(git bc)###plu = pull upstreamplum = pull upstream masterpluc = !git pull upstream $(git bc)pbum = pull --rebase upstream masterpbuc = !git pull --rebase upstream $(git bc)###rb = rebaserba = rebase --abortrbc = rebase --continuerbi = rebase --interactiverbs = rebase --skip###re = resetrh = reset HEADreh = reset --hardrem = reset --mixedres = reset --softrehh = reset --hard HEADremh = reset --mixed HEADresh = reset --soft HEADrehom = reset --hard origin/master###r = remotera = remote addrr = remote rmrv = remote -vrn = remote renamerp = remote pruners = remote showrao = remote add originrau = remote add upstreamrro = remote remove originrru = remote remove upstreamrso = remote show originrsu = remote show upstreamrpo = remote prune originrpu = remote prune upstream###rmf = rm -frmrf = rm -r -f###s = statussb = status -s -b###sa = stash applysc = stash clearsd = stash dropsl = stash listsp = stash popss = stash savessk = stash save -ksw = stash showst = !git stash list | wc -l 2>/dev/null | grep -oEi '[0-9][0-9]*'###t = tagtd = tag -d###w = showwp = show -pwr = show -p --no-color###svnr = svn rebasesvnd = svn dcommitsvnl = svn log --oneline --show-commit###subadd = !sh -c 'git submodule add git://github.com/$1 $2/$(basename $1)' -subrm = !sh -c 'git submodule deinit -f -- $1 && rm -rf .git/modules/$1 && git rm -f $1' -subup = submodule update --init --recursivesubpull = !git submodule foreach git pull --tags origin master###assume = update-index --assume-unchangedunassume = update-index --no-assume-unchangedassumed = !git ls -v | grep ^h | cut -c 3-unassumeall = !git assumed | xargs git unassumeassumeall = !git status -s | awk {'print $2'} | xargs git assume###bump = !sh -c 'git commit -am \"Version bump v$1\" && git psuoc && git release $1' -release = !sh -c 'git tag v$1 && git pst' -unrelease = !sh -c 'git tag -d v$1 && git pso :v$1' -merged = !sh -c 'git o master && git plom && git bd $1 && git rpo' -aliases = !git config -l | grep alias | cut -c 7-snap = !git stash save 'snapshot: $(date)' && git stash apply 'stash@{0}'bare = !sh -c 'git symbolic-ref HEAD refs/heads/$1 && git rm --cached -r . && git clean -xfd' -whois = !sh -c 'git log -i -1 --author=\"$1\" --pretty=\"format:%an <%ae>\"' -serve = daemon --reuseaddr --verbose --base-path=. --export-all ./.git###behind = !git rev-list --left-only --count $(git bu)...HEADahead = !git rev-list --right-only --count $(git bu)...HEAD###ours = "!f() { git checkout --ours $@ && git add $@; }; f"theirs = "!f() { git checkout --theirs $@ && git add $@; }; f"subrepo = !sh -c 'git filter-branch --prune-empty --subdirectory-filter $1 master' -human = name-rev --name-only --refs=refs/heads/*[filter "lfs"]clean = git-lfs clean -- %fsmudge = git-lfs smudge -- %fprocess = git-lfs filter-processrequired = true


Пример .gitignore:
### Node #### Logslogsnpm-debug.log*yarn-debug.log*yarn-error.log*# Optional npm cache directory.npm# Dependency directories/node_modules/jspm_packages/bower_components# Yarn Integrity file.yarn-integrity# Optional eslint cache.eslintcache# dotenv environment variables file(s).env.env.*#Build generateddist/build/# Serverless generated files.serverless/### SublimeText #### cache files for sublime text*.tmlanguage.cache*.tmPreferences.cache*.stTheme.cache# workspace files are user-specific*.sublime-workspace# project files should be checked into the repository, unless a significant# proportion of contributors will probably not be using SublimeText# *.sublime-project### VisualStudioCode ###.vscode/*!.vscode/settings.json!.vscode/tasks.json!.vscode/launch.json!.vscode/extensions.json### Vim ###*.sw[a-p]### WebStorm/IntelliJ ###/.ideamodules.xml*.ipr*.iml### System Files ###*.DS_Store# Windows thumbnail cache filesThumbs.dbehthumbs.dbehthumbs_vista.db# Folder config fileDesktop.ini# Recycle Bin used on file shares$RECYCLE.BIN/# Thumbnails._*# Files that might appear in the root of a volume.DocumentRevisions-V100.fseventsd.Spotlight-V100.TemporaryItems.Trashes.VolumeIcon.icns.com.apple.timemachine.donotpresent


npm


npm представляет собой пакетный менеджер, позволяющий устанавливать зависимости проекта.

Официальный сайт: npmjs.com.

Установка.

npm устанавливается вместе с Node.js.

Также вместе с Node.js устанавливается npx, позволяющий запускать исполняемые файлы без установки: npx create-react-app my-app.

Проверка установки:

node --version | -vnpm --version | -v

Обновление:

npm i -g npm@latest

Список доступных команд:

npm helpnpm help [command-name]

Инициализация проекта:

npm init// autonpm init --yes | -y

Установка зависимостей

npm install | i// проверка конкретной зависимостиnpm explore [package-name]// проверка всех зависимостейnpm doctor// очисткаnpm ci

Принудительная переустановка зависимостей:

npm i --force | -f

Установка только продакшн-пакетов:

npm i --only=production | --only=prod

Добавление зависимости:

npm i [package-name]npm i [package-name@version]// примерnpm i express

Добавление зависимости для разработки:

npm i --save-dev | -D [package-name]// примерnpm i -D nodemon

Обновление зависимости:

npm update | up [package-name]

Удаление зависимости:

// dependencynpm remove | rm | r [package-name]// devDependencynpm r -D [package-name]

Глобальная установка/обновление/удаление пакета:

npm i/up/r -g [package-name]// примерnpm i -g create-react-app// использованиеcreate-react-app my-app

Определение устаревших пакетов:

npm outdatednpm outdated [package-name]

Список установленных зависимостей:

npm list | ls// top levelnpm ls --depth=0 | --depth 0// global + top levelnpm ls -g --depth 0

Информация о пакете:

npm view | v [package-name]// примерnpm v reactnpm v react.description

Запуск скрипта/выполнение команды:

npm run [script]// пример// package.json: "scripts": { "dev": "nodemon server.js" }npm run dev// script start или node server.jsnpm startnpm stop

Удаление дублирующихся пакетов:

npm dedupe | ddp

Удаление посторонних пакетов:

npm prune

Обнаружение уязвимостей (угроз безопасности):

npm audit// jsonnpm audit --json// plain textnpm audit --parseable

Автоматическое исправление уязвимостей:

npm audit fix

yarn


yarn, как и npm, представляет собой пакетный менеджер, позволяющий устанавливать зависимости проекта.

Официальный сайт: yarnpkg.com.

Установка:

npm i -g yarn

Команда yarn dlx позволяет запускать исполняемые файлы без установки: yarn dlx create-react-app my-app. Для этого yarn необходимо обновить до второй версии: yarn set version berry.

Проверка установки:

yarn --version | -v

Обновление:

yarn set version latest

Список доступных команд:

yarn helpyarn help [command-name]

Инициализация проекта:

yarn init// autoyarn init --yes | -y// "private": true в package.jsonyarn init --private | -p// auto + privateyarn init -yp

Установка зависимостей:

yarn// илиyarn install// тихая установкаyarn install --silent | -s// проверкаyarn --check-files

Принудительная переустановка зависимостей:

yarn install --force

Установка только продакшн-пакетов:

yarn install --production | --prod

Добавление зависимости:

yarn add [package-name]yarn add [package-name@version]// примерyarn add express// тихая установкаyarn add --silent// илиyarn add -s

Добавление зависимости для разработки:

yarn add --dev | -D [package-name]// примерyarn add -D nodemon

Обновление зависимости:

yarn upgrade [package-name]

Удаление зависимости:

yarn remove [package-name]

Глобальная установка/обновление/удаление пакета:

yarn global add/upgrade/remove [package-name]// примерyarn global add create-react-app// использованиеcreate-react-app my-app

Список установленных зависимостей:

yarn list// top levelyarn list --depth=0 | --depth 0

Информация о пакете:

yarn info [package-name]// илиyarn why [package-name]// примерyarn info reactyarn info react descriptionyarn why webpack

Запуск скрипта/выполнение команды:

yarn [script]// илиyarn run [script]// пример// package.json: "scripts": { "dev": "nodemon server.js" }yarn dev

package.json


{  "name": "my-app",  "version": "1.0.0",  "description": "my awesome app",  "keywords": [    "amazing",    "awesome",    "best"  ],  "private": true,  "main": "server.js",  "license": "MIT",  "homepage": "https://my-website.com",  "repository": {    "type": "git",    "url": "https://github.com/user/repo.git"  },  "repository": "github:user/repo",  "author": {    "name": "My Name",    "email": "myemail@example.com",    "url": "https://my-website.com"  },  "author": "My Name <myemail@example.com> (http://personeltest.ru/aways/my-website.com)",  "contributers": [    {      "name": "Friend Name",      "email": "friendemail@example.com",      "url": "https://friend-website.com"    }  ],  "contributors": "Friend Name <friendemail.com> (http://personeltest.ru/aways/friend-website.com)",  "dependencies": {    "express": "^4.17.1"  },  "devDependencies": {    "nodemon": "^2.0.4"  },  "scripts": {    "start": "react-scripts start",    "dev": "nodemon server.js"  }}

  • name название проекта
  • version версия проекта (см. версионирование)
  • description описание проекта (зачем нужен пакет?)
  • keywords ключевые слова (облегчает поиск в реестре npm)
  • private установка значения в true предотвращает случайную публикацию пакета в реестре npm
  • main основная точка входа для функционирования проекта
  • repository ссылка на репозиторий (один из вариантов)
  • author автор проекта (один из вариантов)
  • contributors участники проекта (люди, внесшие вклад в проект)
  • dependencies зависимости проекта (пакеты, без которых приложение не будет работать)
  • devDependencies зависимости для разработки (пакеты, без которых приложение будет работать)
  • scripts команды (выполняемые сценарии, задачи), предназначенные для автоматизации, например, команда yarn dev запустит скрипт nodemon server.js

Полный список доступных полей файла package.json: npm-package.json

Файлы package-lock.json и yarn.lock содержат более полную информацию об установленных пакетах, чем package.json, например, конкретные версии пакетов вместо диапазона допустимых версий.

Версионирование


Каждый пакет имеет версию, состоящую из трех цифр (например, 1.0.0), где первая цифра мажорная версия (major), вторая минорная версия (minor), третья патчевая версия (патч, patch). Новые версии называются релизами.

Увеличение каждой из этих цифр согласно правилам семантического версионирования (semver) означает следующее:

  • major внесение несовместимых с предыдущей версией изменений
  • minor новая функциональность, совместимая с предыдущей версией
  • patch исправление ошибок, незначительные улучшения

Диапазоны версий или допустимые релизы определяются с помощью следующих операторов (компараторов):

  • * любая версия (аналогично пустой строке)
  • <1.0.0 любая версия, которая меньше 1.0.0
  • <=1.0.0 любая версия, которая меньше или равна 1.0.0
  • >1.0.0 любая версия, которая больше 1.0.0
  • >=1.0.0 любая версия, которая больше или равна 1.0.0
  • =1.0.0 только версия 1.0.0 (оператор "=" можно опустить)
  • >=1.0.0 <2.0.0 больше или равно 1.0.0 и меньше 2.0.0
  • 1.0.0-2.0.0 набор версий включительно
  • ^1.0.0 минорные и патчевые релизы (>=1.0.0 <2.0.0)
  • ~.1.0.0 только патчевые релизы (>=1.0.0 <1.1.0)

Подробные сведения о semver: node-semver.

Благодарю за внимание.
Подробнее..

Перевод Автоматизация семантического управления версиями с помощью Maven (SemVer GitFlow Maven)

05.10.2020 10:14:40 | Автор: admin

Вы используете семантический подход к управлению версиями? Вы используете gitflow? Скорее всего, вы знакомы с процессом корректировки версий, создания веток, слияния с master/dev, повторной корректировки версий, борьбы с конфликтами слияния,


В этой статье я кратко объясню процесс выпуска, который мы, по сути, используем для наших библиотек, и то, как мы его автоматизировали. Обычно мы используем подход CI/CD, используя номера сборок, но для наших библиотек мы решили использовать семантическое управление версиями. Я столкнулся с утомительным процессом выпуска, который сопровождает это в нескольких компаниях, и теперь я наконец нашел решение.


Эта статья посвящена Maven, но есть и много альтернатив Gradle.


Пример проекта можно найти на нашей странице GitHub.


Семантическое управление версиями и Git


Семантическое управление версиями это система классификации ваших выпусков. Я уверен, что вы видели такие номера версий, как 1.6.4, 1.7.10, 1.12.2 и другие. Эти цифры обозначают MAJOR.MINOR.PATCH (МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ)


Кроме того, существуют версии SNAPSHOT, которые выглядят одинаково, но с добавлением -SNAPSHOT в конце, например 1.14.4-SNAPSHOT.


Типичный процесс выпуска состоит из следующих шагов:


  1. Создайте release ветку из ветки разработки (здесь и далее ветка разработки development branch).


  2. Измените версию во всех файлах pom.xml с SNAPSHOT (1.2.3-SNAPSHOT) на non-SNAPSHOT (1.2.3) в release ветке.


  3. Увеличьте версию SNAPSHOT на ветке develop(1.2.4-SNAPSHOT).


  4. Когда все будет сделано для release, объедините release ветку с master веткой. Это актуальный релиз.


  5. Слейте master ветку или ветку release обратно в ветку разработки.



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


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


Каковы были мои цели?


Я хочу автоматизировать весь процесс, чтобы не беспокоиться об ошибках.


Текущее состояние master ветви никогда не должно содержать версию SNAPSHOT.


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


Я никогда не хочу иметь дело с merge конфликтами.


Я также хочу использовать его для hotfixes (которые происходят из master ветки).


Плагин gitflow-maven


Возможно, вы знаете плагин версий maven, который легко позволяет устанавливать версии внутри ваших файлов pom.xml. Однако для этого мне необходимо вручную ввести новую версию. Я хочу, чтобы мое решение автоматически выводило следующую версию на основе текущей версии.


К счастью, уже есть инструменты, помогающие достичь моих целей. Поскольку проблема довольно распространена, несколько человек пытались ее решить. Однако из всех найденных мною подключаемых модулей и инструментов поддерживается только один, при этом предоставляющий достаточно параметров конфигурации: gitflow-maven-plugin.


В нем есть именно то, что мне нужно:


Увеличение номеров версий.


Создание release ветки.


Создание ветки hotfix.


Объединение (Merging) веток.


Кроме того, выпуски возможны непосредственно из ветки разработки без дополнительной ветки выпуска. Это очень удобно, поскольку наша компания в любом случае использует CI/CD, что означает, что ветвь разработки всегда находится в готовом к выпуску состоянии.


Все это можно сделать, выполнив цели (goals) maven. Нам нужно только указать плагину, как себя вести и какое число увеличивать.


Некоторые примеры:


Следующие команды показывают несколько примеров, которые я использовал при оценке плагина.


$ mvn gitflow:release-start -B

Создает ветку release из ветки разработки без ввода данных пользователем (-B Batch Mode)


$ mvn gitflow:release

Создает релиз прямо из ветки разработки. Плагин объединяет текущее состояние разработки с master веткой и увеличивает номер версии в ветке разработки.


В этом примере нет флага пакетной обработки, что означает, что пользователю предлагается ввести всю недостающую информацию.


$ mvn gitflow:hotfix-start -B

$ mvn gitflow:hotfix-finish -B -DhotfixVersion=1.8.9b 

На первом этапе создает ветку hotfix из master ветки, затем выпускает исправление с версией исправления 1.8.9b на втором этапе. Все цели плагина могут быть выполнены в любой ветке. Из-за этого я должен указать плагину, какое исправление я хочу выпустить.


Конфигурация


Добавить зависимости плагина достаточно просто, просто добавьте следующее в плагины сборки poms:


<build>    <plugins>        <plugin>            <groupId>com.amashchenko.maven.plugin</groupId>            <artifactId>gitflow-maven-plugin</artifactId>            <version>1.13.0</version>            <configuration>                <!-- optional configuration -->            </configuration>        </plugin>    </plugins></build>

Последнюю версию можно найти на GitHub или в maven central.


Дополнительная настройка хорошо документирована на странице плагинов GitHub. Вот пример конфигурации с выделением некоторых параметров конфигурации:


<configuration>    <!-- We use maven wrapper in all our projects instead of a local maven installation -->    <mvnExecutable>./mvnw</mvnExecutable>    <!-- Dont push to the git remote. Very useful for testing locally -->    <pushRemote>true</pushRemote>    <!-- Set to true to immediately bump the development version when creating a release branch -->    <commitDevelopmentVersionAtStart>false</commitDevelopmentVersionAtStart>    <!-- Which digit to increas in major.minor.patch versioning, the values being 0.1.2 respectively.         By default the rightmost number is increased.         Pass in the number via parameter or profile to allow configuration,         since everything set in the file can't be overwritten via command line -->    <versionDigitToIncrement>${gitflowDigitToIncrement}</versionDigitToIncrement>    <!-- Execute mvn verify before release -->    <preReleaseGoals>verify</preReleaseGoals>    <preHotfixGoals>verify</preHotfixGoals>    <!-- Configure branches -->    <gitFlowConfig>        <productionBranch>master</productionBranch>        <!-- default is develop, but we use development -->        <developmentBranch>development</developmentBranch>    </gitFlowConfig></configuration>

Это отличный плагин, все работает безупречно, когда я запускаю его локально. Есть несколько избранных настроек, чтобы работать с Gitlab CI.


Автоматизация с помощью Gitlab CI


Мы запускаем конвейер CI/CD с использованием Gitlab CI для наших проектов, поэтому каждый новый commit в нашей ветке разработки приводит к созданию snapshot, а merge для master к release.


Как описано выше, моя цель автоматизировать разработку, master merge и связанные с этим обновления версий, а также hotfixes.


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



Вот как выглядит конвейер ветвления разработки в моем примере с дополнительными этапами release. Поэтому, если я хочу создать release, я просто запускаю этап release, и он автоматически устанавливает версию без snapshot, объединяет (merge) ее с master и переводит версию в ветке разработки на следующую версию snapshot. Без каких-либо дополнительных действий с моей стороны.


Заставляем git работать в Gitlab CI


Если вы пробовали использовать команды git внутри Gitlab CI, то знаете: это не совсем просто, поскольку у исполнителей CI нет учетных данных git.


Для простоты я использую токены доступа с правами write_repository. Я назначил для этого специального пользователя, чтобы видеть, какие коммиты были созданы автоматически.


Я ввожу токен через переменную среды GITLAB_TOKEN, которую я установил как protected, и помечаю ветки development, release/* и hotfix/* как защищенные (protected). Таким образом, только сборки на тех ветках имеют доступ к токену.


Я вручную устанавливаю git remote на runner, чтобы среда CI могла отправлять данные в репозиторий. Я использую переменные, предоставленные Gitlab, поэтому мне не нужно ничего жестко кодировать:


$ git remote set-url --push origin "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"

С помощью этого git команды могут выполняться с использованием учетных данных введенного токена доступа. Git также требует некоторой информации о пользователе, выполняющем действия git. Желательно, чтобы пользователь git соответствовал пользователю, чей токен доступа мы используем. В моем примере:


$ git config user.name "Gitlab CI"$ git config user.email gitlab-ci@viesure.io

Благодаря этому я могу легко найти все коммиты git, сделанные CI. Весь фрагмент кода доступен в репозитории примеров.


Собираем все вместе


Теперь, когда я могу получить доступ к git из CI, мне нужно настроить плагин gitflow. Я столкнулся с некоторыми особенностями.


Для простоты я использую предопределенные переменные, чтобы решить, какую цифру версии я хочу увеличить:


MINOR для релизов


PATCH для hotfixes


Можно также передать переменную при выполнении конвейера или получить ее другим способом.


Все цели (goals) передаются с параметром -B, который означает пакетный режим и означает, что пользовательский ввод не будет запрашиваться.


Автоматический Release


$ ./mvnw gitflow: release -B -DgitflowDigitToIncrement = $RELEASE_DIGIT

Полный процесс выпуска относительно прост. Плагин объединяет ветку разработки в master без SNAPSHOT внутри версии, а затем увеличивает версию для разработки. Я просто вызываю цель (goal ) maven и сообщаю ей, какую цифру увеличивать.


Ручная разблокировка


$ ./mvnw gitflow: release-start -B -DgitflowDigitToIncrement = $RELEASE_DIGIT

$ git push origin HEAD

Выпуск вручную начинается с создания ветки выпуска. Технически цифра не нужна, так как увеличение выполняется при слиянии, но если опция увеличения версии для разработки при разветвлении активна, она будет работать с ней из коробки (например, если она позже изменится, или другие проекты используйте разные настройки). Обратите внимание, что мне нужно нажать ветку вручную, поскольку плагин предназначен для локального использования.


$ git symbolic-ref refs/heads/$CI_COMMIT_REF_NAME refs/remotes/origin/$CI_COMMIT_REF_NAME$ ./mvnw gitflow:release-finish -B -DgitflowDigitToIncrement=$RELEASE_DIGIT

Завершение release приносит с собой первый обходной путь. Git хранит ссылку (сокращенно ref) на HEAD всех ваших веток. Однако Gitlab CI не устанавливает все эти ссылки при проверке репозитория. Плагин использует эти ссылки на HEAD для проверки веток. Тем не менее, у него есть правильные локальные ссылки, поэтому я просто создаю недостающую ссылку HEAD.


После этого цель окончательного выпуска объединяет ветвь выпуска в master ветку и ветку разработки, увеличивая версию для разработки.


Исправление (Hotfix)


Исправление следует тому же процессу, что и выпуск вручную, за исключением того, что оно запускается на мастере, а не на разработке.


$ ./mvnw gitflow:hotfix-start -B -DgitflowDigitToIncrement=$HOTFIX_DIGIT$ git push origin HEAD

Hotfix-start создает ветку hotfix, которая уже содержит увеличенную версию.


$ export CURRENT_VERSION=${CI_COMMIT_REF_NAME/hotfix\/}$ git symbolic-ref refs/heads/$CI_COMMIT_REF_NAME refs/remotes/origin/$CI_COMMIT_REF_NAME$ ./mvnw gitflow:hotfix-finish -B -DgitflowDigitToIncrement=$HOTFIX_DIGIT -DhotfixVersion=$CURRENT_VERSION

Hotfix-finish объединяет его в master ветку и ветку разработки. Однако для исправления требуется еще один параметр: версия исправления. Причина этого опять же в том, что плагин разработан для локального использования, где у вас может быть несколько веток. Он не ожидает выполнения цели внутри ветки исправления.


Как сказано выше, hotfix-start уже увеличивает номер версии, чтобы обозначить версию исправления. Таким образом, имя ветки уже указывает версию исправления.


Я просто извлекаю версию из имени текущей ветки. Поскольку задание конвейера исправления определено для ветвей исправления, оно будет выполняться только в указанной ветке.


Резюме


Это оно. Благодаря этому вы всего в одном клике от всех изменений версий и ветвлений, которые вам когда-либо понадобятся!


Потребовалось немного усилий, чтобы заставить взаимодействия git работать внутри бегунов CI и обойти отсутствующие ссылки. Но теперь, когда это работает, я очень доволен результатом.


Мне больше никогда не придется думать о порядке, в котором мне нужно разветвлять и увеличивать версии. CI позаботится об этом за меня.


Вы можете найти полный сценарий Gitlab CI и пример проекта со всеми приведенными выше фрагментами кода в нашем репозитории GitHub.


Ссылки


Подробнее..

Перевод Автоматизация рабочего процесса Java-проекта с помощью модифицированной модели ветвления Gitflow

06.10.2020 10:05:04 | Автор: admin

Автоматизация рабочего процесса Java-проекта с помощью модифицированной модели ветвления Gitflow


Ключевые выводы


Gitflow это совместная модель ветвления, которая пытается использовать мощность, скорость и простоту ветвления Git. Этот метод хорошо работал в ситуации, которую мы описываем здесь, но другие отмечали, что использование Gitflow сопряжено со своими проблемами.


Документация по использованию Gitflow в размещения в источнике информации в лучшем случае является нечеткой.


Функции изолированы внутри ветвей. Вы можете управлять своими собственными изменениями функций изолированно. Этот подход отличается от разработки на основе магистралей, когда каждый разработчик фиксируется в основной ветке не реже одного раза в 24 часа.


Разделение функций с использованием изолированных ветвей позволяет вам решить, какие функции включать в каждый релиз. Компромисс здесь может заключаться в сложных слияниях.


Обновление от 13 февраля 2019 г.: первоначальное оформление этой статьи вызвало большой отклик, в основном положительный, а иногда и не очень. Основным предметом разногласий было использование нами термина непрерывная доставка в среде, где релиз выполняются вручную. Если вы работаете в команде, которая развертывает сотни релизов в день, наша структура может вам не подойти. Однако, если вы, как и мы, работаете в жестко регулируемой отрасли, такой как финансы, где релиз более контролируемы, и вы хотите максимально использовать возможности ветвления, автоматической интеграции, автоматического размещения и управления версиями, то это решение может работать для вас так же, как и для нас


Много лет назад я был на технологической конференции, где наткнулся на новомодную штуковину на выставке под названием Git. Я узнал, что это инструмент управления версиями нового поколения, и моя первая реакция была такой: Зачем нам это, у нас уже есть SVN? Это было тогда. Сегодня группы разработчиков массово переходят на Git, и вокруг промежуточного программного обеспечения и плагинов возникла огромная экосистема.


Gitflow это совместная модель ветвления, которая пытается использовать мощность, скорость и простоту ветвления Git. Как было написано ранее в InfoQ, этот подход действительно сопряжен со своим набором проблем, особенно в отношении непрерывной интеграции, но это была именно та проблема, которую мы стремились решить. Gitflow был представлен Винсентом Дриссеном в его блоге 2010 года Успешная модель ветвления Git. Gitflow упрощает совместную разработку, позволяя командам изолировать новую разработку от завершенной работы в отдельных ветвях, позволяя вам выбирать функции для релиза, при этом поощряя частые коммиты и автоматическое тестирование. Мы обнаружили, что это дает более чистый код, продвигая регулярные проверки кода во время слияния, даже проверки собственного кода, тем самым выявляя ошибки, возможности для рефакторинга и оптимизации.


Но когда дело доходит до внедрения Gitflow в автоматизированном конвейере развертывания, подробности становятся очень специфичными для вашей среды разработки, и появляются бесконечные возможности. Следовательно, документация является разреженной. Учитывая известные названия ветвей master, development, feature и т.д., какие ветви мы строим, какие тестируем, какие развертываем в нашей команде в виде Snapshot, какие развертывают выпуски и как автоматизируют развертывания в Dev, UAT, Prod и т.д.?


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


Описанный здесь проект использует Java и Maven, но мы считаем, что любая среда может быть адаптирована аналогичным образом. Мы используем GitLab CI с настраиваемыми сценариями запуска, но также можно использовать Jenkins или плагин GitHub CI; мы используем Jira для отслеживания проблем, IntelliJ IDEA в качестве нашей IDE, Nexus в качестве репозитория зависимостей, и мы используем Ansible для нашего автоматического развертывания, но их можно заменить любые аналогичные инструменты.


Эволюция


В допотопные времена разработчики тратили недели или месяцы на создание функции приложения, откуда они передавали завершенную работу интегратору благонамеренному и преданному человечеству парню, который брал все такие функции, интегрировал их., разрешал конфликты и подготавливал к релизу. Процесс интеграции был пугающим, чреватым ошибками, непредсказуемым по графику и последствиям, породив заслуженное название интеграционный ад. Затем на рубеже веков Кент Бек выпустил свою книгу Объяснение экстремального программирования, в которой отстаивал концепцию непрерывной интеграции; практика, когда каждый разработчик создает и интегрирует код в основную ветвь и запускает тесты в автоматическом режиме каждые несколько часов каждый день. Вскоре после этого появился Круиз-Контроль Мартина Фаулера Thoughtworks с открытым исходным кодом, один из первых в истории инструментов автоматизации CI.


Вход в Gitflow


Gitflow рекомендует использовать feature ветки для разработки отдельных функций и отдельные ветки для интеграции и релиза. Следующий рисунок, перепечатанный из блога Винсента Дриссена, теперь хорошо знаком командам разработчиков Git.



Как пользователи Git, все мы знакомы с веткой под названием master; это основная ветка или trunk, создаваемая Git по умолчанию при первой инициализации любого проекта Git. Прежде чем принять Gitflow, вы, скорее всего, делали коммит в своей основной ветке.


Запуск Gitflow


Чтобы запустить проект с помощью Gitflow, существует однократный шаг инициализации, на котором вы создаете ответвление от master под названием develop. С этого момента develop становится всеобъемлющей ветвью, где весь ваш код размещается и тестируется, по сути, становясь вашей основной ветвью интеграции.



Как разработчик, вы никогда не будете делать коммит непосредственно в ветке develop, и вы никогда не будете делать коммит непосредственно в ветке master. Master стабильная ветка, которая содержит только те работы, которые готовы к производству, либо выпущены, либо готовы к релизу. Если что-то находится в мастере, то это прошлый или будущий производственный релиз.


Бранч develop именуется "нестабильной". Возможно, это название немного ошибочно он стабилен тем, что содержит код, предназначенный для выпуска; и он должен быть скомпилирован и испытания должны пройти. Он называется так только потому, что он содержит работу, которая может быть или не быть завершенной, и, следовательно, "нестабильной".


Итак, где мы работаем? Вот где начинает материализоваться остальная часть картины:


Вы получаете новую задачу от Jira, над которой нужно работать. Сразу же вы разветвляете функциональную ветку, обычно от разработки, если она находится в стабильной точке, или от master:



Мы договорились о том, что наши функциональные ветки называются feat-, за которыми следует номер проблемы Jira. (Если существует более одной проблемы Jira, просто используйте задачу Epic или Parent либо один из основных номеров задач, за которым следует очень краткое описание функции.) Например, feat-SDLC-123-add-name-field. Префикс feat- обеспечивает шаблон, который CI-сервер может использовать, чтобы идентифицировать это как ветвь функции. Скоро мы увидим, почему это важно. В этом примере SDLC-123 это номер нашей проблемы Jira, который дает нам визуальную ссылку на основную проблему, а оставшееся описание дает нам краткое описание этой функции.


Теперь разработка идет параллельно: все работают над своими функциональными ветками одновременно, некоторые команды работают над одной и той же ветвью, выполняя эту функцию, другие работают над разными функциями. Мы обнаружили, что за счет частого слияния с ветвью разработки команда сократила время, затрачиваемое на ад слияния (merge hell).


Релизы, Snapshots и общие репозитории


Давайте проясним это несколькими словами. На большинстве предприятий есть репозиторий с одной зависимостью, например Sonatype Nexus. Это репо содержит два вида двоичных файлов. Бинарные файлы SNAPSHOT обычно именуются с использованием версии semver (разделенных точками из трех частей), за которыми следует слово -SNAPSHOT (например, 1.2.0-SNAPSHOT). Версии исполняемых файлов релиза имеют одно и то же имя, кроме суффикса -SNAPSHOT (например, 1.2.0). Сборки моментальных Snapshot уникальны тем, что каждый раз, когда вы создаете двоичный файл с этой версией Snapshot, он заменяет любой предыдущий двоичный файл с таким же именем. Сборки релизов не такие; как только вы создадите сборку релиза, вы можете сообщить, что двоичный файл, связанный с этой версией, никогда не будет изменен в Nexus.


Теперь представьте, что вы работаете над функцией X, а ваша партнерская команда работает над функцией Y. Вы оба одновременно ответили на разработку, так что у вас обоих одна и та же базовая версия в вашем POM (скажем, 1.2.0-SNAPSHOT). Теперь предположим, что вы запустили свою сборку и развернули ветку функций в Nexus, а вскоре после этого ваша компаньонская команда запустила свою сборку и развернула ее в Nexus. В таком сценарии вы никогда не узнаете, какой двоичный файл функции был в Nexus, поскольку 1.2.0-SNAPSHOT будет относиться к двум различным двоичным файлам, соответствующим двум отдельным ветвям функций (или более, если таких ветвей функций больше!) Это очень часто возникающий конфликт.


GitLab CI


Тем не менее, мы поручаем командам совершать коммиты чаще и быстрее! Итак, как нам избежать таких конфликтов? Необходимо указать GitLab CI, чтобы он построил ошибку, но не развертывал ее в Nexus, связав ветки feat- с этапом жизненного цикла проверки Maven (который создается локально и запускает все тесты), а не этапом развертывания Maven (который будет отправлять двоичный Snapshot в Nexus).


GitLab CI настраивается путем определения файла (с именем .gitlab-ci.yml) в корне проекта, который содержит точные шаги выполнения CI / CD. Прелесть этой функции в том, что сценарий запуска затем связывается с вашим коммитом, поэтому вы можете изменять его в зависимости от комитта или ветки.


Мы настроили GitLab CI со следующим заданием, содержащим регулярное выражение и скрипт для создания веток функций:


feature-build:  stage:     build  script:    - mvn clean verify sonar:sonar  only:    - /^feat-\w+$/

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


Разработка, управляемая покрытием


Сейчас самое время обсудить тестовое покрытие. Идея IntelliJ имеет режим выполнения покрытия, который позволяет вам запускать тестовый код с покрытием (либо в режиме отладки, либо в режиме выполнения) и закрашивает поля в зеленый или розовый цвет, в зависимости от того, был ли этот код покрыт или нет. Вы также можете (и должны) добавить в Maven плагин покрытия (например, Jacoco), чтобы вы могли получать отчеты о покрытии как часть вашей сборки интеграции. Если вы используете среду IDE, которая не окрашивает поля, вы можете использовать эти отчеты, чтобы найти участки непокрытого кода.



[Одно замечание по этому поводу к сожалению, все еще есть много профессиональных команд разработчиков, которые, хотя и проповедуют непреодолимую ортодоксальность автоматизации и разработки, тем не менее по тем или иным причинам упускают возможность расширить охват тестированием. Теперь мы не будем теми, кто скажет таким командам вернуться и добавить тесты к каждому открытому фрагменту кода; но, как хорошие граждане-разработчики, мы считаем своим долгом ввести тесты хотя бы для кода, который мы добавили или изменили. Сопоставляя поля с цветовым кодом покрытия и введенный нами код, мы можем быстро определить возможности для введения новых тестов.]


Тесты выполняются как часть сборки Maven. На этапе тестирования Maven выполняются модульные тесты (обозначенные именем, которое начинается с Test-something.java или заканчивается Test.java, Tests.java или TestCase.java). Maven verifyphase (требуется плагин Maven Failsafe) также выполняет интеграционные тесты. Вызов mvn verify запускает сборку, за которой следует конвейер этапов жизненного цикла, включая тестирование и проверку. Мы также рекомендуем установить SonarQube и плагин Maven SonarQube для статического анализа кода на этапе тестирования. В нашей модели каждый коммит или слияние ветки выполняет все эти тесты.


Интеграция нашей работы


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


У нас также есть политика, которую мы применяем внутри GitLab, которую мы не можем объединить в разработку без проверки кода в форме запроса на слияние:




В зависимости от вашей политики SDLC вы можете заставить разработчиков провести проверку кода с кем-то еще, снабдив свои слияния списком утверждающих. Или вы можете применить более расслабленную стратегию, разрешив разработчикам выполнять свои собственные проверки кода после просмотра своего собственного запроса на слияние. Эта стратегия прекрасно работает, поскольку побуждает разработчиков хотя бы пересматривать свой собственный код, но, как и любая система, она сопряжена с очевидными рисками. Обратите внимание, что, поскольку двоичный файл никогда не будет развернут на Nexus или иным образом предоставлен, версия POM, содержащаяся в ветке разработки, не имеет значения. Вы можете назвать его 0.0.0-SNAPSHOT или просто оставить исходную версию POM, откуда она была разветвлена.


Наконец, по прошествии некоторого количества дней функция будет готова, она будет полностью объединена с версией для разработки и объявлена стабильной, и есть ряд таких функций, которые готовы к релизу. Помните, что на этом этапе мы запускали наши проверочные тесты для каждого коммита, но мы еще не развернули даже SNAPSHOT для Nexus. Это наш следующий шаг


На этом этапе мы разветвляем ветвь релиза от develop. Но в небольшом отступлении от традиционного Gitflow мы не называем это релизом; мы называем ветвь по номеру версии релиза. В нашем случае мы используем семантическое управление версиями из трех частей, поэтому, если это основной релиз (новые функции или критические изменения), мы увеличиваем основной (первый) номер, второстепенный релиз мы увеличиваем второстепенный (второй) номер, а если патч, то третий. Таким образом, если предыдущий релиз был 1.2.0, следующий релиз может быть 1.2.1, а версия моментального Snapshot pom будет 1.2.1-SNAPSHOT. Таким образом, наша ветка будет называться соответственно 1.2.1.


Настройка конвейера


Мы настроили наш конвейер GitLab CI так, чтобы он распознавал создание ветки релиза (ветка релиза идентифицируется по semver номеру, разделенному точкой из трех частей; на языке регулярных выражений: \d+\.\d+\.\d+). Средство выполнения CI/CD сконфигурировано для извлечения имени релиза из имени ветки и для использования плагина версии для изменения версии POM, чтобы включить SNAPSHOT, соответствующий этому имени ветки (1.2.1-SNAPSHOT в нашем примере).


release-build:  stage:    build  script:     - mvn versions:set -DnewVersion=${CI_COMMIT_REF_NAME}-SNAPSHOT    # now commit the version to the release branch    - git add .    - git commit -m "create snapshot [ci skip]"    - git push    # Deploy the binary to Nexus:    - mvn deploy  only:    - /^\d+\.\d+\.\d+$/  except:    - tags

Обратите внимание на [ci skip] в коммит сообщении. Это очень важно для предотвращения зацикливания, когда каждый коммит запускает новый запуск и новый коммит!


После того, как исполнитель CI вносит изменения в POM, он фиксирует и отправляет обновленный файл pom.xml (теперь содержащий версию, которая соответствует имени ветки). Теперь POM удаленной ветви релиза содержит правильную версию SNAPSHOT для этой ветви.


GitLab CI, все еще идентифицирующий эту ветвь релиза по семантическому шаблону управления версиями (/^\d+\.\d+\.\d+$/, например 1.2.1) своего имени, распознает, что в ветке произошло событие push. Средство выполнения GitLab выполняет mvn deploy для создания сборки SNAPSHOT и развертывания в Nexus. Теперь Ansible развертывает его на сервере разработки, где он доступен для тестирования. Этот шаг выполняется для всех нажатий на ветку релиза. Таким образом, небольшие изменения, которые разработчики вносят в релиз-кандидат, запускают сборку SNAPSHOT, релиз SNAPSHOT для Nexus и развертывание этого артефакта SNAPSHOT на серверах разработки.


Мы пропустили сценарии развертывания Ansible, потому что они очень индивидуальны для вашей конкретной модели развертывания. Они могут выполнять любые действия, необходимые для развертывания артефактов, включая перезапуск служб после установки новых артефактов, обновление расписаний cron и изменение файлов конфигурации приложения. Вам нужно будет определить развертывание Ansible специально для ваших конкретных нужд.


И, наконец, мы объединяемся в master, заставляя Git пометить релиз номером версии semver из имени исходной ветки релиза, развернуть весь пакет на Nexus и запустить тесты сонара.


Обратите внимание, что в GitLab CI все, что вам нужно для следующего шага работы, нужно обозначить как артефакты. В этом случае мы собираемся развернуть наш артефакт jar с помощью Ansible, поэтому мы обозначили его как артефакт GitLab CI.


master-branch-build:  stage:    build  script:    # Remove the -SNAPSHOT from the POM version    - mvn versions:set -DremoveSnapshot    # use the Maven help plugin to determine the version. Note the grep -v at the end, to prune out unwanted log lines.    - export FINAL_VERSION=$(mvn --non-recursive help:evaluate -Dexpression=project.version | grep -v '\[.*')    # Stage and commit the binaries (again using [ci skip] in the comment to avoid cycles)    - git add .    - git commit -m "Create release version [ci skip]"    # Tag the release    - git tag -a ${FINAL_VERSION} -m "Create release version"    - git push     - mvn sonar:sonar deploy  artifacts:    paths:    # list our binaries here for Ansible deployment in the master-branch-deploy stage    - target/my-binaries-*.jar  only:    - mastermaster-branch-deploy:  stage:    deploy  dependencies:    - master-branch-build  script:   # "We would deploy artifacts (target/my-binaries-*.jar) here, using ansible  only:    - master

Устранение ошибок


Во время тестирования могут быть обнаружены исправления ошибок. Они исправляются прямо в ветке релиза, а затем снова объединяются для разработки. (Develop всегда содержит все, что было или будет выпущено.)



Наконец, ветвь релиза утверждается, и она объединяется с главной. Master имеет принудительную политику GitLab, чтобы никогда не принимать слияния, кроме как из ветки релиза. Бегун GitLab проверяет объединенный код в master, у которого все еще есть версия SNAPSHOT ветки релиза. Средство выполнения GitLab снова использует плагин версий Maven для выполнения версий: установите цель с помощью набора параметров removeSnapshot. Эта цель удалит -SNAPSHOT из версии POM, и runner GitLab отправит это изменение на удаленный master, пометит релиз, увеличит версию POM до следующей версии SNAPSHOT и развернет ее на Nexus. Это развернуто в UAT для тестирования QA и UAT. Как только артефакт будет одобрен для релиза в производство, группы производственного обслуживания возьмут артефакт релиза и развернут его в производственной среде. (Этот шаг также можно автоматизировать с помощью Ansible, в зависимости от вашей корпоративной политики.)



Патчи и hotfix


Мы должны упомянуть еще об одном побочном рабочем процессе, и это касается патчей или хотфиксов. Они запускаются, когда проблема обнаруживается в производственной среде или во время тестирования артефакта релиза, например, ошибка или проблема с производительностью. Hotfix аналогичны веткам релиза. Они названы по названию релиза, как и ветка релиза. Единственная разница в том, что они разветвлены не от разработки, а от masterа.



Проделана работа по завершению хотфикса. Как и ветка релиза, hotfix запускает развертывание Nexus SNAPSHOT и развертывание в UAT. Как только это сертифицировано, оно снова объединяется с разработкой, а затем сливается с masterом для подготовки к релизу. Master запустит сборку релиза и развернет двоичный файл релиза на Nexus.


Заключение


Мы можем суммировать все это в следующей схеме:



Итак, у нас есть Gitflow. Мы призываем команды разработчиков любого размера изучить и опробовать эту стратегию. Мы считаем, что он имеет следующие достоинства:


Feature изолированы. Легко управлять своими собственными изменениями Feature изолированно, с обычным предупреждением о ветвлении Feature, которое может затруднить интеграцию команды в очень активную Feature, или коммиты не часто объединяются


Разделение Feature, которое позволяет вам выбрать, какие Feature включить в релиз. Альтернативный подход к этому постоянно релизить код, связанный с Feature, которые скрыты за флагами Feature.


Процесс интеграции и слияния привел к тому, что наша команда провела более дисциплинированную проверку кода, что помогло продвинуть чистое кодирование.


Автоматическое тестирование, развертывание и релиз для всех сред, которые соответствуют требованиям наших команд и предпочтительному способу работы.


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


Нам интересно узнать о вашем опыте работы с Gitflow и конвейерами развертывания, поэтому, пожалуйста, оставляйте свои комментарии ниже.


Дополнительная информация


Для более традиционной обработки Gitflow с использованием Atlassian Bamboo и BitBucket см. Здесь.


Существует также отличный плагин Gitflow Maven, который активно поддерживается Алексом Мащенко, который работает так же, как плагин релиза Maven на Gitflow. Это может быть адаптировано к нашей предлагаемой реализации Gitflow.


Об авторах


Виктор Граци работает в Nomura Securities над разработкой приложений для корпоративной инфраструктуры. Победитель Oracle Java, Виктор также работал ведущим редактором очереди Java в InfoQ и был членом комитета по выполнению процессов сообщества Java.


Брайан Гарднер недавний релизник Технологического института Стивенса, где он получил степень бакалавра и магистра в области компьютерных наук. В настоящее время Брайан работает в Nomura инженером-программистом в группе разработки инфраструктуры. В основном он проводит свой день, работая над серверными службами Spring Boot или над конвейерами больших данных с помощью Apache Spark.

Подробнее..

До 40 релизов в день в Enterprise наша сool story

17.06.2021 12:20:28 | Автор: admin

Пару слов о нас: мы команда банка Открытие, которая отвечает за разработку всех розничных фронтов от рабочего места сотрудника в отделении до мобильных приложений физических лиц. В последние пару лет мы переживаем взрывной рост в несколько раз у нас более 400 сотрудников ИТ и мы продолжаем расти и расти. Как оказалось, многие решения, которые были приняты на старте нашей работы, оказались верными. И о некоторых из них мы вам расскажем. Готовы? Поехали!

Один из вопросов на старте цифровой трансформации банка был таким: как нам хранить исходный код используя монорепозиторий для хранения всех миросервисов или много репозиториев? Мы выбрали такой подход: для каждого приложения, библиотеки или микросервиса создаем отдельный репозиторий.

В рамках дискуссий внутри команды для нас было очевидно, что:

  • Монорепоэто решение, которое невзлетает из коробки, так как у нас банально много кода (на сегодня его около 100 GB), который просто будет долго выкачиваться к себе на локальную машину изрепозитория;

  • Когда мы попытаемся открыть такой огромный проект вIDE, она скорее всего приляжет;

  • При одновременной работе сотни разработчиков над одной кодовой базой потенциально ненулевые риски получитьpullrequest-ы на десятки тысяч строк кода, которые очень долго нужно будет вычитывать, чтобы понять,что вообще меняется.Которые имеют непредсказуемые срокиревью, тестирования и сложный вывод впрод;

  • Оставался открытым вопрос разграничения доступа внутрирепозитория(большие компании, которыепиарилиподход смонорепонесколько лет назад,решали этокастомнымсамописом, который разграничивал доступ, например, на уровне папок);

  • Очевидным плюсом было удобстворазруливаниязависимостей (в одномрепозиторииэто можноконтролировать на уровне кода). Так чтоесли отказываться отмонорепозитория, то надо было придумать как решить эту проблему.

Кирпичики, из которых складывается наше решение

Учитывая номер версии МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ, следует увеличивать:

  1. МАЖОРНУЮ версию, когда сделаны обратно несовместимые изменения в работе сервиса;

  2. МИНОРНУЮ версию, когда вы добавляете новую функциональность, ненарушая обратной совместимости;

  3. ПАТЧ-версию, когда вы делаете какие-то внутренние исправления в работе сервиса, которые никак не меняют внешние контракты.

Дополнительные обозначения дляпредрелизныхибилд-метаданных возможны как дополнения к МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ формату.

Для наших разработчиков нет греха страшнее, чем нарушить правила семантическоговерсионирования, мы строго за этим следим.

  • Публикация контрактов

В конвейер сборки сервисов внедрен процесс генерации и публикации контрактов, в которых однозначно описан интерфейс взаимодействия с ними. Доменные сервисы общаются с внешними потребителями наружу черезGraphQL(на самом деле у нас500+ сервисов, которые поделены на доменные группы, наподобие модной нынче и активно продвигаемойdomainorientedmicroservicearchitecture(DOMA). У каждой доменной группы есть только один внешнийGraphQLфасад, а внутри сервисы общаются поRabbitMQ, но для простоты понимания рассмотрим только пример с доменными сервисами). Сам контракт представляет из себя SDL схему, котораягенеритсяпри помощи стандартного функционала из коробкибиблиотекиqraphql-dotnetпри помощи утилитыgraphql-sdl-exporter. На самом деле утилиту мы написали сами, но решили ей поделиться и выложили еев паблик.

Что происходит после коммита:

Весь процесс контроля автоматизирован и встроен в конвейер сборки.

Шаги:

  1. После коммита в любую ветку в случае, если увеличивается минорная версия, то есть у сервиса меняется интерфейс (как правило с добавлением новых элементов в контрактах), запускается процесс генерации контракта. В случае увеличения патч-версии перегенерация контракта не требуется, так как меняется только что-то под капотом без изменения внешнего поведения.

  2. После из этого контракта (SDL схемы) при помощи утилит специфичных для каждого языка генерируются клиенты для различных потребителей на их языках, которые представляют собой пакеты, хранящиеся в репозитории пакетного менеджера. В нашем случае это NuGet, NPM, Graddle для .Net, JS и Java соответственно. В качестве инструмента для хранения и управления пакетов используется Arfifactory. Версия контракта (сервиса-поставщика) соответствует версии пакета. Например, сервис Payments версии 2.1.3 имеет контракт версии 2.1.0 (не 2.1.3, т.к. перегенерация для версий от 2.1.1 и выше не требуется, а потребуется только для 2.2.0).

    В тот момент, когда в Artifactory появится новая версия пакета, у разработчика, который пишет сервис-потребитель, IDE сигнализирует о том, что вышла более новая версия пакета и предложит ему обновить пакет. Увеличение минорной версии, как правило, делается не глядя (мы даже подумываем автоматизировать этот процесс), увеличение же мажорной версии говорит нам о том, что произошли обратно несовместимые изменения и требуется более осознанный подход к обновлению.

    Очень важный момент: Для feature веток и ветки master существуют различные хранилища пакетов: Release для master, Snapshot - для feature-веток. Причем из ветки master нет доступа к пакетам из Snapshot.

Рассмотрим пару примеров, которые продемонстрируют работу такого подхода.

Пусть сервис Accounts версии 4.7.2 потребляет сервис Payments версии 2.1.3 (обращается к нему, используя контракт в виде nuget пакета версии 2.1.0). Появилась необходимость добавить новую фичу в сервис Payments.

Вариант 1:

Допустим, что доработка сервиса Payments будет содержать обратно совместимые изменения. Тогда потребуется увеличить его минорную версию и после доработки она станет 2.2.0, что приведет к перегенерации контракта и клиентов для сервиса Payments, которые тоже станут версии 2.2.0.

Когда разработчиксервиса Payments Вася сделает коммит в свою feature ветку, увеличив версию сервиса, в хранилище Snapshot появятся новые клиенты для сервиса Payments. Разработчик сервиса Accounts Петя сможет обновить свой пакет для работы с сервисом Payments до версии 2.2.0 с новой функциональностью, сможет ей воспользоваться и реализовать свою логику работы с Payments в своей feature ветке своего репозитория.

Пока Вася доводит свой функционал до идеала, Петя допиливает свою функциональность, которая теперь зависит от версии Payments 2.2.0 и подливает свои изменения в ветку master, что в нашем случае приводит к инициации процесса релиза сервиса Accounts на прод. В процессе сборки сервис Accounts пытается разрешить свои зависимости и, в частности, получить новую версию пакета для работы с Payments 2.2.0 из хранилища Release (напомню, что master смотрит только туда), где его нет. Сборка падает с ошибкой. Автоматический контроль сработал. Петя идет к Васе, просит его побыстрее раскатать на прод новую версию сервиса Payments, после чего катит свою новую версию Accounts.

Вариант 2:

В сервисе Payments необходимо сделать обратно несовместимые изменения. Вообще мы такое категорически не приветствуем, но иногда такая необходимость возникает. Вася знает о своих потребителях (откуда скажем позже), он пишет письма ответственным и предупреждает всех о том, что собирается ломать обратную совместимость и увеличить версию своего сервиса до 3.0.0. В день Икс Вася опубликует версию Payments на тестовый контур и ждет результатов интеграционных тестов. У тех, кто не готов к обновлению сервиса Payments, попадают Beta тесты и тестовый контур станет неработоспособным в той части, которая потребляет сервис Payments. Новые контракты Payments при этом будут лежать в Snapshot. Вася не обновляет master-ветку, пока все потребители Payments не обновят свои версии пакетов для работы с Payments и не будут готовы синхроннос ним (сразу после него) раскататься на бой.

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

Кстати, информация о том, кто использует клиента к сервису позволяет построить карту зависимостей системы и визуализировать ее в виде графа.

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru