Автоматизация библиотек на Typescript +15


Хочу сразу оговориться: эта статья не дает готового к использованию рецепта. Это скорее моя история путешествия в мир Typescript и NodeJS, а также результаты моих экспериментов. Тем не менее, в конце статьи будет ссылка на GitLab репозиторий, который вы можете посмотреть, и может быть взять что то понравившееся себе на вооружение. Может быть даже по моему опыту создадите свое автоматизированное решение.

Зачем это нужно


Итак, зачем вообще нужно создавать библиотеки, ну или в конкретном случае NPM пакеты?

  1. Переиспользование кода между проектами.

    Началось все с того, что я заметил за собой привычку создавать в проектах папочку /tools. Кроме этого я еще и забираю с собой большую часть этой папки при переходе в новый проект. И тут я задал себе вопрос, а почему бы вместо копипаста не сделать NPM пакет и потом просто подключать его в любой проект?
  2. Различный жизненный цикл. В одном из приложений в качестве зависимости была большая корпоративная сборка компонентов. Обновлять ее можно было только целиком, даже если обновился только один компонент. Изменения в других компонентах могли что то сломать и не всегда у нас хватало оцененного времени, чтобы это перетестировать. Эта модель очень неудобна. Когда же каждый пакет служит своей цели или небольшому набору связанных по смыслу целей, их уже можно обновлять когда это действительно нужно. Так же следующие версии пакета выпускаются только когда в них есть какие то изменения, а не «за компанию».
  3. Отделить второстепенный код от основной бизнес логики. В DDD есть принцип дистилляции домена, он предполагает идентифицировать части кода не принадлежащие основному домену и изолироваться от них. И как лучше изолироваться, чем вынести этот код в отдельный проект.
    Кстати, дистилляция домена очень похожа на принцип SRP только на другом уровне.
  4. Собственное покрытие кода. В одном из проектов, где я участвовал, покрытие кода было около 30%. А библиотека, которую я из него вынес, имеет покрытие около 100%. Проект, хоть и потерял процент покрытия, как был в красной зоне до вынесения, так и остался. А библиотека имеет такие завидные показатели и по сей день, спустя почти год и 4 мажорные версии.
  5. Open Source. Код не содержащий бизнес логику — первый кандидат на отделение от проекта, поэтому его можно сделать открытым.

Стартовать новые библиотеки «дорого»


Cуществует такая проблема: для того чтобы создать библиотеку недостаточно завести под нее git репозиторий. Также нужно настроить таски, чтобы проект можно было собирать, проводить статическую проверку(lint) и тестировать. Также, в дополнение к тестированию желательно собирать покрытие кода. Кроме этого публиковать пакет придется каждый раз вручную. И еще нужно написать readme. Вот только с readme ничем помочь не смогу.

Итак, что же можно сделать со всеми этими нудными, неинтересными задачами?



Первый шаг: Seed


Начал я все с того, что создал проект seed. Этакий стартовый набор, у него была такая же структура, как и у первого моего проекта по вынесению кода в отдельный пакет. Я насоздавал в нем gulp таски и сценарии которые бы построили, протестировали и собрали покрытие пакета в одно действие. Теперь, чтобы создать еще один проект, мне нужно было склонировать seed в новую папку и поменять origin, чтобы он показывал на свежесозданный на GitHub-е репозиторий(тогда я еще использовал GitHub).

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

И это работало для меня какое то время. До тех пор пока я не использовал seed в проекте, где участвовали несколько разработчиков. Я написал инструкцию из трех шагов: взять последний master, построить и опубликовать. И в какой то момент один из разработчиков, по непонятной причине выполнил первый шаг и третий. Как вообще такое возможно?

Второй шаг: Автоматическая публикация


Несмотря на то, что это была единичная ошибка, такие ручные действия как публикация занудны. Поэтому я посчитал, что автоматизировать ее необходимо. Кроме этого нужен был CI, чтобы предотвратить попадание «красных» коммитов в master. Сначала я попробовал использовать Travis CI, но уперся в следующее ограничение. Он считает pull-request в master эквивалентным коммиту в master-е. А мне нужно было сделать разные действия.

Один из моих коллег посоветовал обратить внимание на GitLab и его CI, и там заработало все что я хотел.

Я создал следующий процесс работы с проектом, применяемый когда нужно поправить ошибку, добавить новую функциональность или создать новую версию:

  1. Создаю ветку от master-а. Пишу в ней код и тесты.
  2. Создаю merge request.
  3. GitLab CI автоматически запускает тесты в контейнере node:latest
  4. На запросе проходит Code Review.
  5. После того как запрос помержен, GitLab запускает второй набор скриптов. Этот набор создает тэг на коммите с номером версии. Номер версии берется из package.json если он там вручную увеличен, если нет, то берется последняя опубликованная версия и автоинкрементируется.
  6. Скрипт собирает проект и снова запускает тесты.
  7. Последними шагами тэг с версией отправляется в репозиторий а пакет публикуется в NPM.

Таким образом, версия указанная в тэге всегда совпадает с версией пакета, опубликованного с этого коммита. Для того чтобы эти операции работали, в проекте на GitLab нужно указать переменные окружения с ключами доступа в репозиторий и в NPM.

Последний шаг: Автоматизируем всё


На этот момент я уже многое автоматизировал, но все еще оставалось достаточно много ручных действий для того чтобы создать проект. Это, конечно, все равно уже был прогресс, потому что действия делались один раз на проект, а не на каждую версию. Но все равно, инструкция состояла из 11 шагов. И я сам пару раз ошибся, делая эти шаги. Тогда я и решил, что раз начал автоматизировать, то надо довести это до конца.

Чтобы эта полная автоматизация работала, но компьютере мне нужно иметь 3 файла в папке .ssh. Я посчитал, что эта папка достаточно защищена, раз там уже хранится приватный ключ id_rsa. Этот файл также будет использован для того чтобы GitLab CI передавал тэги в репозиторий.

Второй файл, который я туда положил — «gitlab», в нем лежит ключ доступа к GitLab API. И третий файл — «npm», ключ доступа для публикации пакета.

И тут начинается магия. Все что нужно для создания нового пакета это запустить одну команду в папке seed: «gulp startNewLib -n [npmName]/[libName]». Готово, пакет создан, готов к разработке и авто-публикации.

Для примера, пакет «vlr/validity» был создан таким образом.

Эта команда делает следующие действия:

  1. Cоздает проект на GitLab
  2. Клонирует seed в локальную папку по соседству с папкой из которой команда запущена.
  3. Меняет origin на проект созданный в шаге 1
  4. Пушит ветку master
  5. Создает переменные окружения в проекте из файлов в папке .ssh
  6. Создает ветку firstImplementation
  7. Меняет имя библиотеки в package.json, коммитит и пушит ветку

Все что нужно после этого — положить туда код и создать merge request.

Как результат, которым можно гордиться, от момента когда принимается решение вынести какой то код в отдельный проект до публикации первой версии проходит около пяти минут. Четыре из которых занимают два пайплайна GitLabCI, и одна минута на то чтобы запустить вышеописанную команду, перетащить код, и нажать кнопки в интерфейсе GitLab чтобы создать и потом помержить запрос.

Есть некоторые ограничения: GitLab имя должно совпадать с именем в npm. И еще, эта команда, в отличие от остального функционала в проекте seed, работает только на Windows.

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




К сожалению, не доступен сервер mySQL