Popstas
Записки о Linux

NPM version: версионирование, история изменений, деплой

13.04.2020, 18:45 - 5 мин читать

Как я веду красивую историю изменений в своих проектах и деплою одной командой после коммита (нет, это не git hooks).

npm version - классный механизм версионирования, встроенный в NPM.

Версионирование

Главная, казалось бы, цель команды: отмечать версии.

Из коробки это значит, что вы можете написать npm version <version> и запустить цепочку задач по подготовке новой версии. Варианта собственно три:

  • npm version patch - 0.0.1 -> 0.0.2
  • npm version minor - 0.0.2 -> 0.1.0
  • npm version major - 0.1.0 -> 1.0.0

Но можно и принудительно версию вписать, но только по semver.

Что произойдёт:

  1. В package.json изменится версия
  2. Выполнится scripts.version из package.json, если он есть
  3. Создастся коммит с package.json и версией в названии
  4. На этот коммит поставится тег версии
  5. Запустится scripts.postversion из package.json, если он есть

Это уже немало:

  • Не надо высчитывать версию, просто выбираете patch, minor или major в зависимости от содержания изменений
  • Не надо ставить тег
  • Вы не забудете изменить номер версии в package.json
  • В postversion вы можете вписать дальшейшие шаги (релиз, деплой). По-хорошему надо релизить после прохождения CI, но кого это волнует

Команда не сработает, если Git грязный, но можно обойти это с --force, я часто обхожу.

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

Дальше - интереснее.

Автоматический Changelog

CHANGELOG.md генерится утилитой conventional-changelog.

Об этом я уже писал 4 года назад. Там я описывал свои поиски, здесь опишу, к чему я пришёл.

С тех пор я все коммиты называю по Angular Conventions. Не пугайтесь “Angular” в названии, никакой привязки там нет.

В 2 словах о соглашениях:

Пишите feat: новая фича или fix: исправлен баг такой-то. Этого уже достаточно, чтобы генерить красивые истории изменений.

Подробнее, насколько я пользуюсь:

Сообщение имеет структуру:

type(scope): subject

body

footer

Я обычно ограничиваюсь:

type: subject

body

Типы, в порядке, как я использую:

  • feat - новый функционал
  • fix - исправления ошибок
  • chore - правки скриптов деплоя и т.п.
  • refactor - правки кода без изменения функциональности
  • docs - правки текстов
  • style - правки отступов
  • test - добавление тестов

В changelog попадают только feat и fix.

В футере принято перечислять ссылки на связанные задачи.

Если в футер добавить BREAKING CHANGE: что-то ломающее обратную совместимость, то это также попадёт в changelog. По semver принято менять мажорную версию, если случился BREAKING CHANGE, но я не придерживаюсь строго.

В package.json:

{
  "scripts": {
    "version": "npm run changelog && git add CHANGELOG.md",
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
  }
}

CHANGELOG.md при таком конфиге попадёт в коммит версии, что удобно, да и коммиты версий становятся не такими уж бесполезными.

Можно поставить standard-changelog чисто под ангуларовский формат, я по историческим причинам его не использую.

Пример CHANGELOG.md

Красивые releases на Github

conventional-github-releaser использует тот же механизм извлечения описания из коммитов, но пишет не в файл, а в релиз на Github.

В package.json:

{
  "scripts": {
    "postversion": "git push && npm run release",
    "release": "conventional-github-releaser -p angular"
  }
}

Вы можете не хотеть автоматом запускать release при версионировании, тогда думаю вы знаете, что делать.

Не помню, обязательно ли пушить перед запуском conventional-github-releaser, скорее да, чем нет, я всегда пушу.

Подмена версий в коде

Что если нужно поменять версию не только в package.json? Тогда нужно писать скрипты.

У меня случаи несложные, такие:

Поменять версию в docker-compose.yml

Отсюда:

#!/bin/bash
set -eu
version="$(cat package.json | grep '"version": "[0-9]' | cut -d':' -f2  | cut -d'"' -f2)"
echo "$version"
sed -i 's/shop-list:.*/shop-list:v'"${version}"'/g' docker-compose.yml

Поменять версию в userscript

Отсюда:

#!/bin/bash
set -eu
version="$(cat package.json | grep '"version": "[0-9]' | cut -d':' -f2  | cut -d'"' -f2)"
echo "$version"
sed -i 's/@version.*/@version        '"${version}"'/g' planfixfix.user.js

sed -i на MacOS из коробки не работает, чтобы исправить, поставьте brew install gnu-sed и добавьте alias sed=gsed (это неточно, пишу по памяти).

Скрипт надо добавить в секцию scripts.version вашего package.json, добавить изменённый файл в Git, чтобы он попал в коммит версии:

{
  "scripts": {
    "version": "bash scripts/version-update.sh && git add changed-file.js",
  }
}

Публикация в NPM

Это нужно только js проектам и то далеко не всем. Тут всё просто: добавьте npm publish в секцию scripts.release вашего package.json.

В package.json:

{
  "scripts": {
    "postversion": "npm run release",
    "release": "npm publish"
  }
}

Деплой куда угодно

В секцию release можно вписать любой скрипт, который будет деплоить.

Например, в одном моём проекте по npm version генерится статическое приложение и деплоится на Github Pages скриптом deploy.sh.

В package.json:

{
  "scripts": {
    "postversion": "npm run release",
    "release": "npm run deploy",
    "deploy": "bash scripts/deploy.sh"
  }
}

Не только JavaScript

Это неочевидно, но npm version можно использовать для проектов на любом языке, единственное условие: наличие package.json.

Я, например, использовал такой способ для ведения CHANGELOG.md для некоторых сайтов на PHP.

Пример package.json

Типичный пример для моих новых проектов:

{
  "name": "packagename",
  "version": "0.0.1",
  "description": "Description",
  "scripts": {
    "version": "npm run changelog && git add CHANGELOG.md",
    "postversion": "git push && npm run release",
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
    "release": "conventional-github-releaser -p angular"
  },
  "author": "Stanislav Popov",
  "license": "ISC"
}

Установка пакетов

Чтобы всё работало, нужно скачать эти утилиты:

npm install -g conventional-changelog-cli conventional-github-releaser

Не вижу смысла добавлять эти пакеты в зависимости, хотя раньше так делал.

Если в процессе npm version что-то пошло не так

При отладке этих процессов вы неизбежно где-то ошибётесь и окажетесь в ситуации, когда сценарий не прошёл полностью, а коммит версии уже есть.

Тогда надо откатывать:

  1. Удалите тег: git tag v0.1.2 -d.
  2. Удалите коммит: git reset --hard HEAD~. Если кроме автоматических изменений в коммите было что-то ещё, аккуратнее тут, лучше тогда без --hard.
  3. Если коммит уже запушился и никто не успел заметить (аккуратнее в команде), удалите коммит оттуда: git push --force и тег отдельно удалите: git push origin :v0.1.2 (он не удалится по push).
  4. Руками удалите релиз с Github, сначала переведите его в draft, потом можно будет удалить.