Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → SemVer: версии и управление релизами

Семантическое версионирование: SemVer, changelog и процесс релизов

Как нумеровать версии осмысленно: правила SemVer, мажор/минор/патч на примерах, conventional commits, генерация changelog и релизный процесс для библиотеки и сервиса.

Прямой ответ

SemVer (MAJOR.MINOR.PATCH) — контракт с потребителем, а не «красивые номера»: MAJOR растёт на обратно несовместимое изменение (потребитель должен что-то поменять в своём коде/конфиге), MINOR — на новую функциональность с сохранением совместимости (обновился — всё работает + появилось новое), PATCH — на совместимые исправления багов; 0.y.z — стадия «обещаний нет» (API устаканивается, ломать можно в минорах — честный сигнал), pre-release-суффиксы (1.2.0-rc.1) — для кандидат-версий. Практическая ценность: потребитель по номеру версии понимает, чего ждать от обновления (патчи ставит смело, миноры читает changelog, мажоры планирует миграцию) — и наоборот, команда, выпускающая ломающее изменение в PATCH, разрушает доверие и автоматизацию (Dependabot-подобные обновляторы полагаются на SemVer — обновления). Обвязка процесса: conventional commits (feat:, fix:, feat!:/BREAKING CHANGE: — тип коммита определяет следующую версию и генерирует changelog автоматически — release-please/semantic-release), changelog по Keep a Changelog (группировка Added/Changed/Fixed, ссылки на задачи — changelog пишется для людей, не дамп git log), и релиз из CI тегом (версия в одном месте — git-тег, из него собирается артефакт/пакет — публикация пакета).

Правила SemVer: спорные случаи и решения

Спецификация короткая, но спорные случаи решаются принципом «что увидит потребитель». Удаление/переименование публичного API — MAJOR (очевидно); изменение поведения при тех же сигнатурах — MAJOR, если поведение было частью контракта (функция возвращала null, стала кидать исключение — вызывающий код сломается без изменения компиляции); исправление бага, ломающее полагающихся на баг — формально MAJOR (баг был наблюдаемым поведением — на практике — минор с заметной записью в changelog, если полагавшиеся маргинальны; решение документируется); обновление мажорной зависимости — MAJOR, если её изменения протекают в ваш публичный контракт (типы в сигнатурах, поведение), иначе PATCH; новый необязательный параметр/метод — MINOR; deprecation — MINOR (пометка устаревания с сохранением работы; удаление помеченного — следующий MAJOR — цикл deprecate→remove даёт потребителям окно миграции). PATCH ≠ «всё мелкое»: новая фича, «очень маленькая и безопасная» — всё равно MINOR (иначе «минор» перестаёт означать «появилось новое»). И обратная ловушка «вечного 0.x» — библиотеки годами на 0.x ломают совместимость в минорах и удивляются отсутствию внедрения: 1.0.0 — это не «идеал», а решение давать обещания.

Conventional commits и автоматизация

Формат: type(scope): description — feat(auth): добавлен вход по QR (MINOR), fix(api): исправлена пагинация пустых страниц (PATCH), feat!: новый формат ответа или тело с BREAKING CHANGE: (MAJOR); типы — feat/fix/docs/refactor/test/chore (refactor/test — не влияют на версию). Инструменты: commitlint в pre-commit (хуки — формат проверяется локально), semantic-release/release-please в CI: анализ типов коммитов с прошлого тега → расчёт следующей версии → генерация changelog → тег и публикация (полный автомат: мерж в main — релиз через минуты — связка с trunk-based полная). Дисциплина, которую автоматика требует от людей: осмысленный выбор типа (fix вместо feat занижает версию и ломает потребителей; ! на каждый чих — обесценивает MAJOR), breaking change описывается телом коммита (что сломано и как мигрировать — текст попадёт в changelog дословно), и понимание: автоматизация не принимает решений за вас — она исполняет решения, закодированные в типах коммитов.

Changelog и релизные заметки

Changelog — для потребителей, не археологов: формат Keep a Changelog (Unreleased-секция копится между релизами; выпуски датируются; группировка Added/Changed/Deprecated/Removed/Fixed/Security), каждая запись — «что изменилось для использующего» (исправлен расчёт скидки на пустой корзине, не фикс #482), breaking-изменения — с инструкцией миграции (до/после кодом); ссылки на PR/задачи — в конце записи (трассируемость без шума). Автоматический changelog из conventional commits — база, которая дописывается руками в ключевых местах (миграции, заметные изменения поведения — автоматика не знает, что важно потребителю). Релизные заметки ≠ changelog для библиотек с частыми выпусками (полный changelog в репо, в GitHub Release — заметное); для продуктов с пользователями — наоборот, release notes — маркетингово-человеческий слой поверх технического changelog. Версионирование документов/API: changelog API — отдельный артефакт (версии API), документация публикуется по версиям (пользователь старой версии читает своё — не «актуальное, которое уже про другое»).

Практический сценарий: библиотека с 0.x выходит на 1.0

Исходник: внутренняя Python-библиотека на 0.7.x, потребители — пять команд; боль: миноры ломают (0.x позволяет), потребители пинят версии и не обновляются месяцами. План выхода на 1.0: (1) аудит публичного API — что считается контрактом (экспортируемое в __init__/документированное — остальное помечается внутренним и выносится из экспорта — «публичное» сужается осознанно); (2) ввод conventional commits + commitlint (месяц истории коммитов в новом формате); (3) deprecation-цикл: всё, что будет ломаться в 1.0, помечено DeprecationWarning в 0.9 (минор с предупреждениями — потребители видят их в логах заранее); (4) релиз 1.0.0: semantic-release настроен (тег → сборка → PyPI — публикация), changelog 1.0.0 — полный гайд миграции с 0.x; (5) политика зафиксирована в README: обещания SemVer, цикл deprecate-минор/remove-мажор, поддержание последних двух мажоров патчами. Результаты полугодия: потребители обновляются патчи автоматически (Dependabot + зелёные тесты — доверие к SemVer окупается), миноры — по changelog за минуты, первая реальная поломка (нужно убрать устаревший метод) — запланированный 2.0.0 с миграцией — ноль аварийных тикетов; «обновление библиотеки» перестало быть событием риска.

FAQ

SemVer для внутреннего сервиса без внешних потребителей — нужен? В облегчённом виде полезен: теги версий дают точки отката и читаемость «что задеплоено» (git-sha + семантический тег для релизов), changelog деплоев помогает в инцидентах («когда это сломалось — между 1.4 и 1.5»); полная SemVer-механика — когда появляются внутренние потребители-сервисы с контрактами (тогда и версионирование API).

Почему 0.x «без обещаний», если библиотека стабильна? Спецификация: 0.x — разработка публичного API (любое изменение допустимо в минорах); «стабильная 0.x годами» — противоречие, которое решается выпуском 1.0.0 (обещать совместимость) — страх «1.0 = идеал» не по спецификации: 1.0.0 означает «мы приняли решение не ломать без MAJOR», не «код безупречен».

Календарное версионирование (CalVer) — когда вместо SemVer? Когда совместимость не главный контракт потребителя: дистрибутивы (Ubuntu 24.04), продукты с регулярными релизами без обещаний API (JetBrains-подобные 2024.2) — дата релиза информативнее семантики; библиотеки, от которых зависят кодом, — SemVer (потребителю нужна совместимость, не дата).

Как версионировать монорепо с несколькими пакетами? Два подхода: независимые версии пакетов (каждый живёт своим SemVer — точность, сложнее процесс) и единая версия монорепо (все пакеты растут вместе — простой процесс, «пустые» релизы пакетов без изменений — fixed-режим Babel-класса); выбор — по связанности пакетов и терпимости потребителей к пустым минорам; инструменты (changesets, lerna, release-please с компонентами) поддерживают оба.

Источники: semver.org, Conventional Commits 1.0.0, Keep a Changelog — ссылки в шапке.