Инженерия

Проверка changelog для GitHub Actions

5 мин чтения

У каждой команды, которая ведёт changelog вручную, был один и тот же разговор после одного и того же инцидента: релиз вышел без записи, кто-то спрашивает почему, и честный ответ — человек, который бы её написал, спешил, и шаг changelog жил только в памяти. Автоматизация changelog разбирает, что pipeline может безопасно автоматизировать, а что всё ещё требует человека; проверка changelog в CI — вторая половина этой проблемы, потому что автоматизация написания не помогает, если никто не обязан вообще её запускать. GitHub Actions — это то место, где большинство команд уже запускают свои проверки pull request’ов, так что и эта проверка живёт там же.

Почему «мы просим людей добавлять запись» проваливается по предсказуемой схеме?

Потому что это конкурирует за внимание со всем остальным в pull request’е, и это единственная часть без немедленного последствия за пропуск. Тесты проваливаются громко и блокируют merge. Отсутствующая запись changelog не блокирует ничего, поэтому она проигрывает, как только кто-то торопится, что на практике большую часть времени. Политика, поддерживаемая памятью, деградирует именно с той скоростью, которую можно ожидать: хорошо первые несколько недель после согласия всех, потом тихо заброшена, как только человек, которому было не всё равно, уходит в отпуск или меняет команду.

Что на самом деле проверяет CI-check для записи changelog?

Не качество написанного, только то, что запись существует и правильно оформлена, что является правильным объёмом для проверки changelog, работающей в CI, а не в голове человека. Распространённая форма: check смотрит на diff PR и требует либо новый файл в директории changeset’ов (шаблон, который используют Changesets и похожие инструменты), либо изменённую строку в файле changelog, и проваливает build, если ни того ни другого нет. Проверка того, что запись на самом деле говорит, по-прежнему происходит там, где она всегда происходила, в code review, потому что это суждение не место в скрипте.

Что проверяет CI-checkЧто не проверяет
Есть changeset или строка changelog в diffЯсна ли формулировка
Запись ссылается на правильный пакет, в monorepoЗаслуживает ли изменение записи вообще
Файл синтаксически валиден (frontmatter, форма JSON)Честна ли запись о влиянии

Нужна ли она каждому PR, или некоторые изменения освобождены?

Некоторые освобождены, и список исключений — то место, где такие системы на самом деле строятся или забрасываются. Обновление зависимости без видимого эффекта, изменение только тестов, внутренний рефакторинг без изменения поведения: ни одно из этого не должно заставлять контрибьюторку выдумывать запись changelog для того, до чего никому, читающему changelog, нет дела. Работающий шаблон — метка или флаг, которую контрибьюторка может применить (no-changelog-needed) и которая удовлетворяет CI-check без файла, проверяемый тем, кто одобряет PR, так что само исключение проходит ту же проверку, через которую прошла бы запись.

Что происходит с законными исключениями, вроде срочного хотфикса?

Gate относится к merge, а не к деплою: хотфикс под реальным временным давлением может смержиться с записью-заполнителем или тикетом на доработку, при условии что CI-check удовлетворяется намерением, а не только законченным абзацем; некоторые команды принимают однострочный stub, который мейнтейнерша шлифует перед следующим релизным срезом. Чего gate никогда не должен позволять — тихо пропустить шаг, потому что забытый stub — меньший провал, чем запись, которой никогда не существовало, а stub хотя бы оставляет след, который кто-то может найти позже.

# .github/workflows/changelog-check.yml
on:
  pull_request:
    types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
  changelog:
    if: >-
      !contains(github.event.pull_request.labels.*.name,
      'no-changelog-needed')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # нужна базовая ветка
      - name: Require changelog entry
        run: |
          base="origin/${{ github.base_ref }}"
          if ! git diff --name-only "$base"...HEAD \
              | grep -q '^\.changeset/'; then
            echo "No changeset. Add one, or have a maintainer"
            echo "apply the no-changelog-needed label."
            exit 1
          fi

Как убедиться, что сама проверка корректна, прежде чем она начнёт блокировать реальные PR?

Сначала откройте черновой pull request в одноразовую ветку: один с changeset’ом, один без него и один с меткой исключения, и убедитесь, что все три получают ожидаемый результат, прежде чем проверка начнёт применяться к чужой работе. Проверка changelog, которая проваливается открыто, пропуская каждый PR из-за условия, написанного задом наперёд, хуже, чем отсутствие проверки вообще, потому что выглядит как покрытие, которого на самом деле нет. workflow_dispatch на том же файле, запущенный вручную на паре недавно смерженных PR, ловит большинство таких ошибок без необходимости в живом pull request.

Работает ли та же идея вне GitHub Actions?

Форма переносится, меняется только синтаксис. GitLab CI выражает то же правило как блок rules джобы, проверяющий $CI_MERGE_REQUEST_LABELS вместо if в GitHub Actions, а обязательное одобрение merge request может заменить собой шаг проверки исключения. Проверка, описанная в этой статье, сделана на GitHub Actions, потому что это платформа, на которой уже находится большинство команд, читающих её, но лежащее в основе требование — gate, проверяемый машиной, а не соглашение, о котором просто попросили, — одинаково везде, где CI запускается перед merge.

Работает ли это так же в monorepo?

Нужна ещё одна часть: для какого пакета эта запись. Changelog в monorepo разбирает, почему единый общерепозиторный файл перестаёт работать, как только пакеты выпускаются независимо; CI-check наследует то же требование; changeset, не называющий пакет, не является полезным доказательством того, что нужный changelog обновится, только того, что какой-то файл где-то изменился в diff. Инструменты, построенные для этого (Changesets — распространённый в экосистеме JavaScript), просят контрибьюторку выбрать затронутый пакет и semver-приращение в тот же момент, когда создаётся changeset, так что CI-check получает обе части бесплатно вместо того, чтобы выводить их позже.

FAQ

Должен ли CI-check блокировать merge, или только предупреждать? Блокировать. Предупреждение функционально идентично вежливой просьбе, которая уже провалилась. Метка исключения существует именно для того, чтобы у настоящего случая «только предупреждение» всё равно был законный путь через тот же строгий gate.

Кто проверяет, правильно ли применена метка исключения? Тот, кто одобряет pull request, как часть проверки, которую он и так уже делает. Метка никогда не должна применяться самостоятельно и без проверки, иначе она становится той же тихой лазейкой, которую gate должен был закрыть.

Заменяет ли принуждение в CI необходимость pipeline автоматизации changelog? Нет, оно его питает. Автоматизация changelog разбирает превращение структурированных записей в страницу, ленту и письмо; CI-check — то, что гарантирует существование этих структурированных записей, чтобы их вообще было что автоматизировать.

Какая самая маленькая версия этого стоит того, чтобы построить первой? Один check, который проваливается, если не изменился ни один файл под назначенной директорией changelog, с одной меткой исключения. Маршрутизация по пакетам и вывод semver для monorepo могут прийти позже; главная привычка — запись существует, или кто-то явно сказал, что она не нужна, — то, что стоит иметь с первого дня.


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

По теме на changeloop: Сравнение инструментов changelog, Генератор changelog

changeloop
Команда, которая делает changelog, замыкающий цикл. Пользователи о чём-то просят, ваша команда это делает, тот, кто просил, узнаёт об этом.