Предпосылки
Это продолжение моей предыдущей статьи: Монорепо с использованием Lerna, обычных коммитов и пакетов Github. Предварительные условия предыдущей статьи предполагают понимание этой, поэтому вы можете сначала прочитать ее для дополнительного контекста.
Если вы застряли на каком-то этапе статьи или просто хотите получить код прямо сейчас, его можно найти в репозитории.
Контекст
Непрерывная интеграция (CI)
Непрерывная интеграция — это практика разработки программного обеспечения, заключающаяся в максимально частой интеграции кода. Перед интеграцией кода обычно выполняется ряд проверок, таких как запуск тестов или компиляция/сборка проекта, с целью обнаружения ошибок, чем раньше, тем лучше.
Обычной практикой является автоматическое выполнение этих проверок при открытии нового запроса на извлечение или даже отправке кода в репозиторий, чтобы заставить все эти проверки пройти, прежде чем мы сможем безопасно интегрировать изменения в кодовую базу.
Действия на гитхабе
Действия Github — это функция Github, которая позволяет разработчикам выполнять рабочие процессы, когда в наших репозиториях происходят определенные события, такие как отправка кода или закрытие запроса на извлечение (часто используется в сценариях непрерывной интеграции). Действия Github бесплатны для общедоступных репозиториев.
Эти рабочие процессы организованы в jobs
, steps
и actions
во вложенном виде и запускаются одним или несколькими events
. Каждый рабочий процесс представляет собой отдельный файл, написанный на языке YAML.
Что будем строить?
Мы собираемся автоматизировать управление версиями и публикацию пакетов в нашем монорепозитории с помощью Lerna (с обычными коммитами) и действий Github.
Мы собираемся реализовать два разных рабочих процесса Github:
1 — Checks
рабочий процесс: когда мы открываем новый запрос на вытягивание или вносим изменения в открытый запрос на вытягивание, он запускает ряд проверок, которые мы считаем необходимыми для интеграции изменений в нашу кодовую базу.
2 — Publish
рабочий процесс: всякий раз, когда запрос на слияние объединяется, мы запускаем рабочий процесс, который будет версионировать и публиковать наши пакеты. Он будет вести себя немного по-разному в зависимости от ветки назначения:
- При слиянии с веткой
development
будут публиковаться бета-версии измененных пакетов (подходят для контроля качества или тестирования). - При слиянии с веткой
main
будут опубликованы окончательные версии (готовые к производству).
Мы начнем с существующего монорепозитория, в котором уже есть два пакета javascript, которые я создал для этой предыдущей статьи.
На следующем рисунке показаны рабочие процессы, которые мы будем реализовывать в терминологии действий Github:
Руки вверх
Часть 1 — Проверка рабочего процесса при открытии/изменении PR
Github ожидает, что рабочие процессы будут расположены в ${projectFolder}/.github/workflows
, поэтому давайте создадим новую ветку Github и добавим наш первый рабочий процесс checks.yaml
в этот каталог (вы также можете создавать рабочие процессы из пользовательского интерфейса Github):
Структура проекта выглядит так:
/
.github/
workflows/
checks.yaml
[...]
Теперь давайте начнем работать над рабочим процессом. Откройте файл checks.yaml
в редакторе и добавьте следующие атрибуты:
name: Checks # Workflow name
on:
pull_request:
types: [opened, synchronize] # Workflow triggering events
name
: имя рабочего процесса.on
: прослушиватель событий, запускающих этот рабочий процесс. В нашем случае он будет срабатывать каждый раз, когда запрос на включение открывается или модифицируется.
Далее мы добавим задание в рабочий процесс и настроим тип экземпляра, который Github будет запускать для его запуска с атрибутом runs-on
:
name: Checks
on:
pull_request:
types: [opened, synchronize]
jobs: # A workflow can have multiple jobs
checks: # Name of the job
runs-on: ubuntu-latest # Instance type where job will run
Эта работа будет состоять из нескольких шагов:
Checkout
: Получите код из репозитория, в котором определен рабочий процесс.Setup NodeJS
: Настройте NodeJS с определенной версией.Setup npm
: Поскольку мы будем устанавливать зависимости из нашего частного реестра (в пакетах Github), мы должны добавить их в конфигурацию npm.Install dependencies
: Установите необходимые пакеты npm.Run tests
: Выполните тесты, если они есть.
В реальном проекте, вероятно, мы выполняем другие шаги, такие как проверка синтаксиса с помощью линтера, сборка проекта или запуск любой другой проверки/процесса, который мы считаем необходимым, чтобы пометить изменения как действительные, прежде чем интегрировать их в кодовую базу.
Пользовательские и общедоступные действия
Для некоторых из упомянутых шагов мы напишем команды с нуля, но для других мы воспользуемся существующими публичными действиями, которые были созданы сообществом и доступны на рынке Github.
Публичные действия используют ключевое слово uses
, а пользовательские команды (одна или несколько строк) используют ключевое слово run
.
Давайте реализуем первые два шага задания build
:
name: Checks
on:
pull_request:
types: [opened, synchronize]
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: "Checkout" # Download code from the repository
uses: actions/checkout@v2 # Public action
with:
fetch-depth: 0 # Checkout all branches and tags
- name: "Use NodeJS 14" # Setup node using version 14
uses: actions/setup-node@v2 # Public action
with:
node-version: '14'
- Шаг
Checkout
загрузит код из репозитория. Мы должны добавить опциюdepth: 0
, чтобы Lerna могла правильно отслеживать теги опубликованных версий пакетов и предлагать новые версии при обнаружении изменений. - На шаге
Use NodeJS 14
мы настраиваем NodeJS для использования версии 14, но мы могли бы даже выполнить его для нескольких версий одновременно, используя матрицу.
Давайте зафиксируем и отправим эту версию рабочего процесса на Github, а затем откроем запрос на извлечение (если у вас еще нет созданной ветки development
, создайте ее из main
, потому что мы откроем для нее запрос на извлечение).
Как только запрос на слияние будет открыт, наш рабочий процесс будет выполнен. Откройте браузер и перейдите в раздел «Действия» репозитория, чтобы увидеть результат выполнения:
Если мы нажмем на него, мы сможем увидеть детали выполнения, а нажав на любое из заданий (в нашем случае на задание checks
), мы сможем увидеть статус и результаты каждого из его шагов:
Давайте добавим следующий шаг: Setup npm
. На этом шаге мы добавим наш реестр пакетов Github в файл .npmrc
, чтобы npm мог найти пакеты, опубликованные в нашем реестре пакетов Github.
В каждом пошаговом действии может выполняться одна или несколько команд. В этом случае мы запустим пару команд npm set
в одном действии:
name: Checks
on:
pull_request:
types: [opened, synchronize]
jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: "Use NodeJS 14"
uses: actions/setup-node@v2
with:
node-version: '14'
- name: "Setup npm" # Add our registry to npm config
run: | # Custom action
npm set @xcanchal:registry=https://npm.pkg.github.com/xcanchal
npm set "//npm.pkg.github.com/:_authToken=${{ secrets.PUBLISH_PACKAGES }}"
Секреты рабочего процесса и переменные среды
В предыдущем фрагменте вы заметили ${{ secrets.PUBLISH_PACKAGES }}
. Не рекомендуется загружать секреты в наши репозитории, и на Github есть решение для этого. Мы добавим новый секрет репозитория, который сможет использовать его позже в наших рабочих процессах.
В браузере перейдите в Репозиторий › Настройки › Секреты и нажмите Новый секрет репозитория. Добавьте описательное имя и значение, которое должно быть токеном личного доступа Github с разрешениями на чтение и запись пакетов в ваш реестр пакетов Github (см. мою предыдущую статью для получения инструкций о том, как его создать).
Помимо наших пользовательских секретов, Github всегда добавляет список переменных в среду выполнения рабочего процесса, таких как имя ветки или хэш фиксации, которые мы также можем использовать. Полный список доступен здесь.
Далее мы добавим еще один шаг: Install dependencies
. На этом шаге мы установим корневые зависимости в производственном режиме (см. команду npm ci), а также запустим lerna bootstrap
для установки зависимостей для каждого из наших пакетов и создадим связи между ними.
name: Checks
on:
pull_request:
types: [opened, synchronize]
jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: "Use NodeJS 14"
uses: actions/setup-node@v2
with:
node-version: '14'
- name: "Setup npm"
run: |
npm set @xcanchal:registry=https://npm.pkg.github.com/xcanchal
npm set "//npm.pkg.github.com/:_authToken=${{ secrets.PUBLISH_PACKAGES }}"
- name: Install dependencies
run: | # Install and link dependencies
npm ci
npx lerna bootstrap
Зафиксируйте и отправьте изменения и посмотрите, как событие «Pull Request synchronized» запускает наш рабочий процесс, который теперь содержит последние добавленные шаги:
Перед добавлением нашего последнего шага Running tests
нам нужно внести изменения в наши пакеты date-logic
и date-renderer
, изменив тестовый скрипт npm. Поскольку мы еще не реализовали ни одного фактического теста, мы просто выведем «ТЕСТЫ ПРОЙДЕНЫ» при выполнении этой команды.
Измените тестовый сценарий в package.json
пакета date-logic
.
# package.json
"scripts": {
"test": "echo TESTS PASSED"
}
и внесите изменения в репо. Затем повторите тот же процесс для date-renderer
.
# commit and push
$ git add .
$ git commit -m "feat(date-logic): echo tests"
$ git push
После отправки новой тестовой команды в наши пакеты мы можем добавить шаг Running tests
в наш рабочий процесс.
name: Checks
on:
pull_request:
types: [opened, synchronize]
jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: "Use NodeJS 14"
uses: actions/setup-node@v2
with:
node-version: '14'
- name: "Setup npm"
run: |
npm set @xcanchal:registry=https://npm.pkg.github.com/xcanchal
npm set "//npm.pkg.github.com/:_authToken=${{ secrets.PUBLISH_PACKAGES }}"
- name: Install dependencies
run: |
npm ci
npx lerna bootstrap
- name: Run tests # Run tests of all packages
run: npx lerna exec npm run test
Отправьте изменения в репозиторий и посмотрите результаты выполнения в разделе действий Github:
Поздравляю! мы завершили нашу первую работу и половину этого урока.
Часть 2. Рабочий процесс публикации в объединенном PR
Создайте файл publish.yaml
в репозитории workflows
со следующим содержимым. Вы заметите, что мы добавили новый атрибут branches
в прослушиватели событий. С этой конфигурацией мы сообщаем Github, что этот рабочий процесс выполняется только тогда, когда запрос на слияние объединяется с веткой development
или main
.
name: Publish
on:
pull_request:
types: [closed]
branches:
- development
- main
Теперь мы добавим в этот рабочий процесс задание с именем publish
, атрибут runs-on
и новый, который мы еще не использовали: if
. Этот атрибут используется для оценки выражения для условного запуска задания, если оно оценивается как истинное или ложное (его также можно использовать в шагах).
В соответствии с атрибутом on
, который мы настроили, этот рабочий процесс будет запускаться при каждом событии «Закрытие запроса на извлечение» для development
или main
, но на самом деле мы хотим, чтобы он выполнялся ТОЛЬКО тогда, когда запрос на извлечение был объединен (не отброшен). Поэтому мы должны добавить к заданию условие github.event.pull_request.merged == true
:
name: Publish
on:
pull_request:
types: [closed]
branches:
- development
- main
jobs:
publish:
if: github.event.pull_request.merged == true # Condition
runs-on: ubuntu-latest
Теперь давайте воспроизведем те же самые первые три шага, которые мы добавили в рабочий процесс проверок (Checkout
, Use NodeJS 14
и Setup npm
).
name: Publish
on:
pull_request:
types: [closed]
branches:
- development
- main
jobs:
publish:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: "Use NodeJS 14"
uses: actions/setup-node@v2
with:
node-version: '14'
- name: "Setup npm"
run: |
npm set @xcanchal:registry=https://npm.pkg.github.com/xcanchal
npm set "//npm.pkg.github.com/:_authToken=${{ secrets.PUBLISH_PACKAGES }}"
Наконец, мы добавим последний (и интересный) шаг: Publish and version
. Разберем подробно атрибуты шага и команды внутри действия:
- Поскольку Лерна будет отвечать за публикацию новых версий пакетов, мы должны установить переменную среды
GH_TOKEN
с нашим токеном личного доступа в качестве значения, чтобы у Лерны были необходимые разрешения. - Нам нужно добавить пару строк конфигурации Github, чтобы указать имя пользователя и учетные данные электронной почты, чтобы Лерна могла делать коммиты и создавать теги для новых версий в репозитории. Для этого воспользуемся переменной
github.actor
, доступной в среде. - В операторе if/else мы проверяем переменную
${{ github.base_ref }}
, чтобы узнать, является ли целевая ветвь PRdevelopment
. В этом случае мы отправим флаги--conventional-prerelease
и--preid
команде версии Lerna для создания бета-версий. В противном случае (это может быть толькоmain
, потому что мы ограничили на уровне рабочего процесса, что это должна быть одна из этих двух ветвей), мы будем использовать аргумент--conventional-graduate
для создания окончательных версий. И последнее, но не менее важное: флаг--yes
автоматически подтверждает версию и операции публикации (в противном случае Lerna запросит ручное подтверждение, и CI завершится ошибкой).
name: Publish
on:
pull_request:
types: [closed]
branches:
- development
- main
jobs:
publish:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: "Use NodeJS 14"
uses: actions/setup-node@v2
with:
node-version: '14'
- name: "Version and publish" # Interesting step
env:
GH_TOKEN: ${{ secrets.PUBLISH_PACKAGES }}
run: |
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor}}@users.noreply.github.com"
if [ ${{ github.base_ref }} = development ]; then
npx lerna version --conventional-commits --conventional-prerelease --preid beta --yes
else
npx lerna version --conventional-commits --conventional-graduate --yes
fi
npx lerna publish from-git --yes
Давайте зафиксируем новый рабочий процесс в репозитории, а затем объединим запрос на слияние, чтобы он сработал. Если мы проверим вывод шага Version and publish
, мы увидим много информации о двух шагах, которые выполнил Лерна:
1) При запуске команды lerna version
были обнаружены изменения в пакетах и предложены новые бета-версии (обратите внимание на префикс -beta.1
), которые были приняты автоматически. После этого он отправил теги версии в репозиторий Github:
2) При выполнении команды lerna publish from-git
она проанализировала последние теги Github, чтобы определить версии, которые необходимо опубликовать, и опубликовала пакеты в реестре пакетов Github.
Итак, теперь у нас есть несколько тестовых версий в нашем реестре пакетов Github:
Предположим, что они прошли тестирование и помечены как готовые к производству. Давайте создадим новый Pull Request от development
против master
, объединим его и посмотрим, как выполняется тот же самый Publish
джоб, но на этот раз Lerna опубликует окончательные версии:
Заключение
Мы видели, насколько мощной может быть пара команд Lerna (в сочетании с надлежащей традиционной историей коммитов) для рабочих процессов непрерывной интеграции наших монорепозиториев.
Автоматизируя эти шаги, мы можем забыть о необходимости вручную определять версии для всех наших пакетов и, таким образом, избегать человеческих ошибок. В этом случае мы использовали действия Github для этого, но любой другой инструмент, такой как Jenkins или CircleCI, тоже подойдет.
Следующие шаги
- Настройте правила защиты веток Github, чтобы заблокировать кнопку слияния запросов на вытягивание в случае сбоя рабочего процесса
checks
. - Настройте средство проверки синтаксиса фиксации (например, commitlint), чтобы избежать человеческих ошибок, которые могут повлиять на управление версиями из-за противоречивой истории фиксации.
Первоначально опубликовано на https://dev.to 4 октября 2021 г.