Как настроить CI/CD-конвейер с помощью Husky и GitHub Actions
CI/CD – одна из основных практик в современной экосистеме разработки программного обеспечения. Она помогает agile-командам создавать высококачественное программное обеспечение за короткие циклы выпуска.
В этом уроке вы узнаете, что такое CI/CD, и я помогу вам настроить конвейер CI/CD с помощью Husky и GitHub Actions в приложении Next.js.
Этот урок предполагает, что вы уже знакомы с React и Next.js или другими современными JavaScript-фреймворками. Вам также понадобится учетная запись GitHub, и базовые знания Git будут весьма полезны.
Если у вас уже есть работающее веб-приложение, созданное не на Next.js, вы все равно можете найти эту статью полезной. Все концепции и большинство конфигураций будут работать с небольшими изменениями в приложениях, созданных на других фреймворках.
Что такое CI/CD?
Непрерывная интеграция/непрерывная доставка или непрерывное развертывание (CI/CD) – это практика, которая предполагает автоматизацию процесса создания, тестирования и развертывания программного обеспечения.
Ее основное преимущество – ускорение всего процесса разработки. Она также повышает производительность, обеспечивая плавную интеграцию кода, внедрение стандартов и лучших практик безопасности. Кроме того, CI/CD помогает сократить цикл обратной связи и выявить проблемы на ранней стадии, о чем мы расскажем ниже.
CI/CD – это важный инструмент в современной практике разработки программного обеспечения, позволяющий командам быстро, эффективно и надежно создавать высококачественное ПО.
Давайте узнаем о нём подробнее.
Что такое CI?
Непрерывная интеграция – это практика разработки программного обеспечения, которая подразумевает, что разработчики в команде объединяют изменения кода в центральном репозитории несколько раз в день.
Вместо того чтобы иметь независимые среды разработки и объединять изменения в определенное время, разработчики часто интегрируют свои изменения в приложение в общую ветку или «ствол».
Что такое CD?
CD в CI/CD обычно означает непрерывную доставку (Continuous Delivery). Это практика, которая поверх CI автоматизирует процесс интеграции, тестирования и выпуска программного обеспечения. Автоматизация останавливается непосредственно перед развертыванием в производство, где необходим шаг, контролируемый человеком.
Но CD также может означать непрерывное развертывание, которое добавляет автоматизацию к шагу выпуска программного обеспечения в производственную среду.
Несмотря на то что CD обычно относится к Continuous Delivery, оба термина иногда используются как взаимозаменяемые. Разница между ними заключается в количестве автоматизации, реализованной в проекте.
Что такое конвейер CI/CD и каковы его преимущества?
Вместе эти две практики создают конвейер CI/CD. Добавление CI/CD в ваш проект даёт следующие преимущества:
- Ускоренная разработка: сокращение времени, необходимого для создания новых функций, благодаря автоматизации сборки, тестирования и развертывания.
- Улучшенное взаимодействие: поощряет частые интеграции кода и снижает количество конфликтов при интеграции.
- Повышение качества кода: способствует внедрению стандартов кодирования и лучших практик во всей кодовой базе.
- Раннее обнаружение проблем: сокращает цикл обратной связи, позволяя заблаговременно выявлять проблемы.
- Повышение производительности: избавляет разработчиков от необходимости работать над повторяющимися задачами.
Вот некоторые из причин, по которым CI/CD является основной практикой в современной разработке программного обеспечения и почему эта тема так важна для изучения. Следующие шаги проведут вас через процесс создания конвейера CI/CD для вашего проекта.
Как создать конвейер CI/CD
Шаг 1: Настройка приложения Next.js
Если у вас уже есть работающее веб-приложение, вы можете пропустить этот пункт и сразу перейти к первому шагу.
В противном случае давайте создадим базовое приложение Next.js с конфигурацией ESLint по умолчанию и Vitest и разместим его в репозитории GitHub.
Создание приложения Next.js
Перейдите в каталог, где вы хотите создать новую папку проекта, а затем выполните следующую команду в терминале:
npx create-next-app@latest
Когда вам будет предложено выбрать варианты установки, убедитесь, что вы выбрали использование ESLint в вашем проекте. Это обеспечит правильную установку ESLint и создание скрипта lint
в package.json
.
Подождите, пока create-next-app
создаст папку и установит зависимости проекта. После этого перейдите в новую папку и запустите сервер разработки:
cd <your-project-name>
npm run dev
Настройка Vitest
Давайте добавим Vitest в проект и добавим несколько автоматизированных тестов для запуска в конвейере CI/CD.
Сначала установите vitest
и необходимые dev-зависимости:
npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react
Создайте файл vitest.config.js
(или vitest.config.ts
, если вы используете TypeScript) со следующим содержимым:
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
},
})
И наконец, добавьте тестовый скрипт в package.json
:
"test": "vitest --no-watch"
Обратите внимание, что я добавил опцию no-watch
в тестовый скрипт. Это предотвращает запуск Vitest в стандартном режиме watch
в dev-среде.
Теперь вы можете добавлять тесты для своего проекта.
Размещение проекта на GitHub
Войдите в свой аккаунт GitHub и создайте новый репозиторий. После этого вы можете соединить локальный репозиторий с только что созданным, добавив этот репозиторий в качестве удаленного. Затем внесите изменения:
git add .
git commit -m "first commit"
git remote add origin git@github.com:<your-user-name>/<your-repo-name>.git
git push origin main
Теперь вы готовы перейти к самой интересной части этого урока.
Шаг 2: Установка Git Hook
Git-хук – это скрипт, который позволяет вам запустить некоторое событие в жизненном цикле Git. В данном случае мы будем использовать Husky.
Husky – это предварительный хук для Git, который позволяет вам поддерживать качество кода, выполняя некоторые задачи при фиксировании или продвижении. Вы можете выполнять различные проверки перед фиксацией новых изменений, такие как линтинг кода и запуск автоматизированных тестов.
Выполнение этих проверок позволит вам избежать потери времени и ресурсов, заранее отлавливая проблемы до запуска рабочего процесса GitHub Actions.
Начнем с добавления Husky в проект с помощью следующей команды:
npm install --save-dev husky
Далее давайте настроим проект с помощью команды husky init
:
npx husky init
После выполнения этой команды вы заметите, что в папке ./husky
был создан файл pre-commit. Кроме того, в package.json
был добавлен скрипт «prepare»
.
Если вы откроете файл pre-commit внутри ./husky
, вы найдете следующее содержимое:
npm test
Как следует из названия, этот файл содержит код, который выполняется перед завершением коммита. Если все настроено так, как описано, тесты будут выполняться каждый раз, когда вы пытаетесь создать новый коммит, и новые коммиты будут добавляться только в том случае, если все тесты пройдут.
Добавление новых git-хуков
Теперь давайте изменим содержимое файла pre-commit, чтобы перед созданием нового коммита также выполнялся линт кода.
Вы можете открыть предпочитаемый вами редактор кода и добавить npm run lint
(или соответствующий скрипт ESLint, если вы не используете Next.js) в новую строку в файле pre-commit. В качестве альтернативы можно просто выполнить следующую команду из корневой папки проекта:
echo "npm run lint" >> ./.husky/pre-commit
Теперь каждый раз, когда вы пытаетесь сделать новый коммит, будут запущены тесты и линтер, и коммит будет создан – только если все тесты пройдут и в коде не будет найдено ошибок.
Настройка lint-staged
Вы можете сделать еще один шаг вперед и включить инструмент под названием lint-staged. Этот инструмент будет особенно полезен, если ваш проект большой, потому что он позволяет запускать Git-хуки только для staged-файлов. В этом случае он будет линтить только те файлы, которые пройдут коммит, что позволит не тратить время на линтинг всего проекта.
Чтобы начать использовать lint-staged, давайте добавим его в проект как зависимость dev:
npm install --save-dev lint-staged
Существуют различные способы настройки lint-staged, и вы можете выбрать тот, который лучше всего подходит для ваших нужд. Я добавлю скрипт и объект lint-staged
в package.json
моего проекта со следующим содержанием:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest --no-watch",
"prepare": "husky"
},
"lint-staged": {
"*.{js, jsx,ts,tsx}": [
"eslint --fix"
]
},
Теперь я могу заменить npm run lint
на npm run lint-staged
в файле pre-commit.
Каждый раз, когда я буду делать новый коммит, все js
, jsx
, ts
или tsx
staged файлы будут подвергаться линтингу, и, если в них есть проблемы, они будут автоматически исправлены.
Давайте проверим, что хук pre-commit работает так, как ожидалось:
- Выполнение
git add
- Выполнение
git commit
- Ожидание запуска линтера и ввод сообщения о коммите при появлении запроса
- Запуск
git log
для подтверждения того, что коммит был создан правильно
Если вы хотите, вы можете добавить больше проверок в файл pre-commit, чтобы соответствовать потребностям вашего проекта. Например, вы можете запустить такой инструмент, как Prettier, для автоматического форматирования кода или commitlint для проверки сообщений фиксации.
Теперь давайте перейдем к настройке рабочего процесса GitHub Actions для проекта.
Шаг 3: Создание рабочего процесса GitHub Actions
Когда первая часть работы завершена, мы можем перейти к следующему шагу. Здесь вы добавите рабочий процесс GitHub Actions, чтобы обеспечить плавную интеграцию изменений во весь проект.
Основы действий на GitHub
GitHub Actions – это CI/CD-платформа, позволяющая автоматизировать сборку, тестирование и развертывание вашего проекта. Она также позволяет выполнять действия, когда в вашем репозитории происходят определенные действия, например, открытие запроса на внесение изменений или создание проблемы.
Действия GitHub настраиваются с помощью рабочих процессов, определенных в файлах YAML. Эти процессы обычно запускаются при наступлении события в репозитории, но их также можно запланировать или запустить вручную.
Рабочие процессы располагаются в папке .github/workflows
и запускают различные задания. Каждое задание включает в себя набор шагов, которые выполняются в порядке очереди на одном и том же исполнителе или сервере. Шаг может быть либо сценарием оболочки, либо действием (многократно используемый фрагмент кода, который помогает сократить количество повторяющегося кода в рабочих процессах).
Давайте соберем все это вместе, создав первый рабочий процесс.
Создание рабочего процесса, который будет выполняться при переносе в основную ветку
Сначала создайте папку .github/workflows/
в корне проекта. Затем создайте файл run-test.yml
. Вы будете добавлять содержимое в этот файл для создания рабочего процесса CI.
Первая строка необязательна и включает в себя имя рабочего процесса. Оно будет отображаться на вкладке «Действия» в репозитории GitHub:
name: Run linter and tests on push
Затем с помощью клавиши on
вы определите событие или события, которые будут запускать рабочий процесс. Это может быть событие в вашем репозитории или временное расписание. В данном случае давайте установим, что он будет запускаться каждый раз, когда происходит обновление репозитория:
on:
push
Вы также можете задать параметры под ключевым словом on
, чтобы ограничить выполнение рабочего процесса некоторой веткой или файлами – например, запускать только при нажатии на главную ветку:
on:
push:
branches:
- main
Ниже добавляется ключ jobs
. Он группирует все задания в рабочем процессе, за которым следует имя первого задания, в данном случае run-linter-and-tests
.
Строки ниже определяют свойства рабочего процесса, настраивая его для запуска на последней версии бегуна Ubuntu Linux и группируя все шаги, которые выполняются на этом задании.
jobs:
run-linter-and-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: npm i
- name: Lint code
run: npm run lint
- name: Run tests
run: npm test
Как уже говорилось, каждый шаг может быть либо сценарием оболочки, либо действием. Вы можете увидеть разницу между первым и вторым шагом в предыдущем коде.
В первом шаге с помощью ключевого слова uses
указывается, что будет выполняться actions/checkout
. Это действие используется для проверки репозитория на устройстве запуска, чтобы рабочий процесс мог использовать код репозитория. Второй шаг Install dependencies
использует ключевое слово run
, чтобы указать заданию выполнить команду npm i
на устройстве запуска.
Вот полный результирующий файл:
name: CI workflow
on:
push
jobs:
run-linter-and-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: npm install
run: npm i
- name: Lint code
run: npm run lint
- name: Run tests
run: npm test
Давайте зафиксируем изменения и отправим их в репозиторий GitHub.
Теперь при каждой отправке изменений в репозиторий будет срабатывать рабочий процесс. Если вы перейдете на вкладку «Действия» в навигационной панели репозитория GitHub, вы найдете список всех запусков всех рабочих процессов и их полные журналы.
Кроме того, вы увидите, что на вкладке «Код» репозитория GitHub рядом с последним сообщением о фиксации появилась зеленая галочка. Это означает, что рабочие процессы были запущены и успешно завершены.
Если задания еще выполняются, вы увидите коричневую точку, а если рабочий процесс завершился с ошибкой - красный крестик.
Добавление второго рабочего процесса для запуска при создании PR
В каждом репозитории может быть один или несколько рабочих процессов, поэтому давайте добавим второй рабочий процесс, который будет запускаться каждый раз, когда создается PR. Давайте запускать отчет о покрытии кода каждый раз, когда PR открывается против основной ветки репозитория.
Сначала создайте и отметьте новую ветку add-wf
:
git checkout -b add-wf
Затем создайте новый YAML-файл в каталоге .github/workflows
и начните добавлять в него содержимое.
Во-первых, давайте добавим имя и время запуска рабочего процесса с помощью ключевого слова on
:
name: Run Coverage on PR
on: pull_request
После этого вы будете использовать ключевое слово jobs
для описания заданий для запуска. Давайте определим первое из них как build-and-run-coverage
, которое будет выполняться в программе ubuntu-latest
:
jobs:
build-and-run-coverage:
runs-on: ubuntu-latest
Теперь добавим шаги (steps
) для этой работы:
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: npm i
- name: Build code
run: npm run build
- name: Run tests and coverage
run: npm run coverage
Ниже приведен полный результирующий код:
name: Run Coverage on PR
on: pull_request
jobs:
build-and-run-coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: npm i
- name: Build code
run: npm run build
- name: Run tests and coverage
run: npm run coverage
Теперь вы можете перенести изменения в репозиторий GitHub:
git add .
git commit -m 'add a wf to run on opened PR'
git push origin add-wf
Теперь вы можете открыть PR против вашей основной ветки и дождаться завершения рабочего процесса.
Отчет об освещении комментариев в PR
Как уже говорилось в этой статье, действия – это многократно используемые фрагменты кода, которые позволяют избежать повторения кода в рабочем процессе. Одна из их особенностей заключается в том, что сообщество уже написало множество таких действий, которые вы можете использовать в своих рабочих процессах, экономя массу времени.
Чтобы завершить созданный нами рабочий процесс, давайте добавим новый шаг, который использует действие, сообщающее о результатах покрытия в виде комментария к запросу на выгрузку.
Сначала изменим ключевое слово permissions
, чтобы обеспечить рабочему процессу правильный доступ к содержимому и созданию комментариев:
permissions:
contents: read
pull-requests: write
Затем давайте воспользуемся действием Vitest Coverage Report, добавив соответствующий шаг (step
) в задание build-and-run-coverage
:
- name: Report Coverage
uses: davelosert/vitest-coverage-report-action@v2
Итоговый yaml
-файл будет выглядеть следующим образом:
name: Run Coverage on PR
on: pull_request
jobs:
build-and-run-coverage:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: npm i
- name: Build
run: npm run build
- name: Run test and coverage
run: npm run coverage
- name: Report Coverage
uses: davelosert/vitest-coverage-report-action@v2
Осталось сделать еще один шаг, чтобы убедиться, что все работает как надо. Вы должны добавить репортер json-summary
в конфигурацию Vitest:
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
coverage: {
provider: "v8",
extension: [".tsx"],
reporter: ['text', 'json-summary', 'json'],
},
},
});
Теперь внесите некоторые изменения в свой проект и добавьте соответствующие тесты, чтобы проверить, работает ли рабочий процесс так, как ожидалось.
Как только вы разместите свои изменения в репозитории GitHub, откройте PR против основной ветки вашего проекта. После завершения работы рабочих процессов вы должны увидеть комментарий, показывающий результат покрытия:
Шаг 4: Развертывание проекта
В качестве последнего шага в этом руководстве давайте развернем проект на Vercel. Вы настроите автоматическое развертывание через Git, которое будет запускать повторное развертывание каждый раз, когда новые изменения будут вноситься или сливаться в основную ветку.
Сначала войдите в свою учетную запись Vercel или создайте ее, если у вас ее еще нет. Затем на панели инструментов нажмите «Добавить новый проект» и нажмите кнопку «Импортировать» рядом с именем вашего репозитория в разделе «Импорт Git-репозитория».
Если вы не видите своего репозитория в списке, это может быть связано с настройками разрешений приложения GitHub. Вы можете управлять ими в разделе настроек вашего аккаунта GitHub.
Наконец, выберите имя проекта в разделе «Настройка проекта» и нажмите на кнопку «Развернуть». Теперь вы можете просмотреть детали развертывания, нажав на ссылку «Развертывание».
Автоматические развертывания Vercel гарантируют, что развернутый проект всегда будет обновляться с учетом последних изменений. Кроме того, они имеют преимущество Preview Deployments – URL предварительного просмотра, который позволяет тестировать новые функции до того, как изменения будут переданы в производство.
Если вы следовали этому руководству, то, выполнив этот шаг, вы завершите CD-часть конвейера CI/CD для своего проекта. Теперь вы можете быть уверены, что любой код, размещаемый в основной ветке, проходит линтинг и тестирование, и после прохождения всех проверок он автоматически переносится в продакшн.
Заключение
В этом руководстве вы узнали о важности CI/CD в современной экосистеме разработки программного обеспечения и его основных преимуществах. Вы также сделали первые шаги в этой области, создав собственный CI/CD-конвейер для своего проекта, научившись использовать Husky и GitHub Actions.
Теперь вы можете продолжать узнавать больше об этих инструментах и совершенствовать свой CI/CD-конвейер, настраивая его в соответствии с потребностями вашего проекта.
Я надеюсь, что вы смогли получить новые знания и получили удовольствие от проделанной работы. Спасибо за прочтение!