Почему номера версий важнее, чем вы думаете
Если вы когда-нибудь задумывались, почему некоторые разработчики покрываются мурашами при виде перехода версии с 1.2.3 на 1.2.4 или почему другие празднуют, как будто выиграли в лотерею, когда им удаётся увеличить основную версию, вы вот-вот узнаете секретный язык версионирования программного обеспечения. Это не магия — это Семантическое версионирование, и это, возможно, самая недооценённая практика в современной разработке программного обеспечения.
Представьте себе ситуацию. Вы поддерживаете критически важную библиотеку, от которой зависят сотни приложений. Вы исправили небольшую ошибку и выпустили версию 2.0.5. Через три дня ваш канал в Slack взрывается. Разработчики сообщают, что их приложения полностью вышли из строя после обновления. Ваше тщательно продуманное исправление ошибки? Оно было связано с непредвиденным критическим изменением, о котором вы не сообщили, и никто — абсолютно никто — этого не ожидал. Теперь вы пишете письма с извинениями вместо того, чтобы писать код.
Этот кошмарный сценарий как раз то, что предотвращает Семантическое версионирование. Это стандартизированный способ сообщить, что содержится в вашем релизе, только через номера версий.
Понимание языка версий
Семантическое версионирование следует обманчиво простой формуле: MAJOR.MINOR.PATCH.
Подумайте об этом так:
- Версия MAJOR (первое число) сигнализирует о том, что вы внесли несовместимые изменения API. Пользователям нужно позаботиться об обновлении — это может нарушить работу их приложений.
- Версия MINOR (среднее число) означает, что вы добавили новые функции, но всё по-прежнему работает так же, как и раньше. Приятные дополнения, без сюрпризов.
- Версия PATCH (последнее число) указывает на исправления ошибок и небольшие настройки, которые пользователи могут применить, не теряя сна.
Вот где это становится прекрасным: когда вы увеличиваете одно число, числа справа сбрасываются до нуля. Так версия 1.5.2 становится 2.0.0 при наличии критических изменений, а не 1.5.3 или какое-либо произвольное число, придуманное вами в пятницу вечером.
Правила увеличения в действии
Позвольте мне показать вам, как это работает:
Начальная версия: 1.2.3
Выпущена исправленная версия → 1.2.4 (увеличение PATCH)
Выпущена новая функция → 1.3.0 (увеличение MINOR, PATCH сбрасывается)
Произведено критическое изменение → 2.0.0 (увеличение MAJOR, MINOR и PATCH сбрасываются)
Как только вы поймёте этот шаблон, вся история вашего программного обеспечения станет читаемой без открытия журнала изменений. Это как чтение электрокардиограммы состояния вашего проекта.
Почему это важно: Ад зависимостей реален
Вот практический кошмар, который решает Семантическое версионирование. Представьте, что ваш проект зависит от трёх библиотек:
- LibraryA (используется для подключения к базе данных);
- LibraryB (зависит от LibraryA);
- LibraryC (также зависит от LibraryA, но требует очень конкретной версии).
Без семантического версионирования вы играете в русскую рулетку каждый раз, когда какая-либо библиотека выпускает обновление. Безопасно ли это? Поломает ли это ваш код? Поломает ли это что-нибудь на трёх уровнях вглубь вашего дерева зависимостей? Никто не знает. Это ад зависимостей.
С Семантическим версионированием ответ встроен в сам номер версии. Если LibraryA переходит с 2.1.0 на 2.1.1, вы знаете, что это безопасно. Если он переходит на 2.2.0, это всё ещё безопасно (новая функция, обратная совместимость). Если он переходит на 3.0.0? Время читать журнал изменений и планировать миграцию.
Реализация в реальном мире: конвейер коммитов и релизов
Давайте перейдём к практике. Большинство команд автоматизируют увеличение версий, используя инструменты Семантического версионирования. Самый популярный подход включает автоматическое инструментирование, такое как semantic-release, которое читает ваши сообщения коммитов и определяет, какое увеличение версии необходимо.
Вот как обычно работает этот процесс:
Магия происходит в сообщениях коммитов. Вам нужно следовать формату обычных коммитов, чтобы это работало. Самое распространённое соглашение — спецификация Conventional Commits:
<type>(<scope>): <subject>
<body>
<footer>
Три типа, которые имеют значение
Коммиты feat приводят к увеличению MINOR версии (новая функция):
feat(auth): добавить поддержку двухфакторной аутентификации
Добавить поддержку аутентификации по SMS для повышения безопасности учётной записи.
Пользователи теперь могут включить 2FA в настройках своей учётной записи.
Коммиты fix приводят к увеличению PATCH версии (исправление ошибки):
fix(api): решить проблему тайм-аута на пользовательском эндпоинте
Пользовательский эндпоинт зависал при высокой нагрузке из-за неэффективного
запроса к базе данных. Оптимизирован запрос для использования индексированных полей.
BREAKING CHANGE в футере запускает увеличение MAJOR версии (несовместимые изменения):
feat(api)!: redesign authentication endpoint
Полный редизайн потока аутентификации. Старый эндпоинт /auth/token
удален в пользу новой реализации OAuth 2.0.
BREAKING CHANGE: эндпоинт /auth/token был удален.
Перейдите на /oauth/token.
Настройка автоматизации: пошаговое руководство
Давайте реализуем это в реальном проекте. Я покажу вам рабочий процесс GitHub Actions, который автоматически обрабатывает версионирование за вас.
Шаг 1: Установка Semantic Release
Сначала добавьте необходимые зависимости в ваш проект Node.js:
npm install --save-dev semantic-release @semantic-release/npm @semantic-release/git @semantic-release/github @semantic-release/changelog
Шаг 2: Настройка Semantic Release
Создайте файл .releaserc.json в корне вашего проекта:
{
"branches": [
"main",
{
"name": "develop",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
[
"@semantic-release/git",
{
"assets": [
"package.json",
"package-lock.json",
"CHANGELOG.md"
],
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
]
}
Шаг 3: Создание рабочего процесса GitHub Actions
Создайте .github/workflows/release.yml:
name: Semantic Release
on:
push:
branches:
- main
- develop
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
packages: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm test
- name: Release
run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Шаг 4: Проверка сообщений коммитов
Прежде чем semantic-release сможет выполнить свою работу, ваши коммиты должны быть в правильном формате. Используйте commitlint, чтобы обеспечить это:
npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
Создайте commitlint.config.js:
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
