Привет! Мы GrandCore Foundation. Создаём идеальную организацию для развития свободных проектов: ПО, этичных онлайн-сервисов и стандартов изделий. Подробнее читайте здесь. Присоединяйтесь к нашему чату в Telegram. Всегда рады единомышленникам!
Для нашего нового проекта — универсального генератора документации у нас появилась потребность в создании монорепозитория, поскольку функционал генератора будет расширяться плагинами. Ниже читайте как мы полностью автоматизировали данный процесс при помощи GitHub Actions и Lerna.
Что такое монорепозиторий
Монорепозиторий — это совокупность множества проектов в одном репозитории.
Преимущества:
Нет необходимости поддерживать огромное количество репозиториев отдельно;
Возможность отслеживания и редактирования кода во всем проекте каждой отдельной командной;
Атомарные коммиты.
Проблемы:
Увеличение объема данных;
Возможная проблема с версионированием каждого подпроекта;
Уменьшение ответственности каждой команды, которая работает над подпроектами репозитория.
Lerna
В качестве основного инструмента работы с монорепозиторием в NPM необходимо рассматривать Lerna. Она является достаточно удобным инструментом для работы над монорепозиториями. Lerna позволяет решить множество возникающих при работе проблем.
Возможности:
Сквозное версионирование;
Индивидуальное версионирование каждого подпроекта;
Удобная публикация всех подпроектов;
Автоматическое управление зависимостями между подпроектами;
Работа с Yarn или NPM.
Создание монорепозитория
Для демонстрации исходных настроек Lerna мы создадим проект по умолчанию, а далее продемонстрируем вариант, который мы реализовали в нашем проекте.
Установите Lerna глобально:
npm i lerna -g
Инициализируйте проект:
lerna init
После этого в корне проекта создаестся директория packages/, файл package.json и файл lerna.json.
Содержимое lerna.json:
{ "packages": [ "packages/*" ], "version": "0.0.0" }
Сначала указывается директория со всеми вложенными проектами (директорий может быть несколько). Далее указывается версия монорепозитория (для сквозного версионирования данное значение совпадает во всех подпроектах).
Cоздание нового пакета (подпроекта):
lerna create <name-pkg>
После ввода данной команды в директории packges/ (по умолчанию) создается новый каталог с именем, которое указывается в качестве параметра. Далее необходимо ответить на несколько стандартных вопросов от Lerna. Важно отметить, что для всех пакетов желательно использовать названия следующего типа @project_name/pkg_name. Таким образом можно привязать все пакеты, которые разрабатываются внутри репозитория, к одному монорепозиторию или одной организации.
Допустим, что мы создали новый пакет. Тогда в директории с этим пакетом создается файл package.json. Чтобы данный пакет в дальнейшем был доступен публично и корректно публиковался, необходимо добавить в данный файл:
"publishConfig": { "access": "public" }
Естественно, подпроекты можно создавать и вручную.
Публикация изменённых проектов:
lerna publish
Возможные проблемы:
Не авторизованы в NPM;
Забыли добавить публичный доступ в
package.jsonпакета;Занятое или некорректное название одного из пакетов.
Важные замечания:
Публикация возможна только после коммита при использовании данной команды в таком виде;
По умолчанию задается сквозная версия для всех пакетов.
Рекомендуем почитать в документации Lerna о дополнительных параметрах данной команды.
В нашем проекте
Если вы хотите использовать собственную структуру проекта, то вы можете её изменить. В качестве примера рассмотрим наш проект универсального генератора документации.
Мы решили оставить в основном модуле только функционал менеджера потока, а также markdown финализатор, совместимый с основными генераторами статических сайтов. Для того, что бы обрабатывать сами файлы с документацией, необходимо подключить соответствующие плагины.
Наш файл lerna.json:
{ "packages": ["plugins/*", "main/"], "version": "0.0.0", "command": { "run": { "npmClient": "npm" } } }
В нашем проекте все плагины находятся в каталоге plugins/, а основной модуль в папке main/. Таким образом структура нашего проекта представляет из себя следующий вид:
├── lerna.json ├── LICENSE ├── main │ ├── index.js │ ├── lib │ │ ├── finMd.js │ │ └── manager.js │ ├── package.json │ └── README.md ├── package.json ├── plugins │ ├── fin-html │ │ ├── index.js │ │ ├── package.json │ │ └── README.md │ └── gen-js-jsdoc │ ├── index.js │ ├── package.json │ └── Readme.md │ ... └── README.md
Взаимодействие модулей и зависимости
Поговорим теперь о взаимодействиях между разными частями проекта. Очевидно, что в монорепозитории каждый подпроект может как-то зависеть от других. Возникает проблема зависимостей между подпроектами. Кроме того, не очень удобно устанавливать зависимости отдельно для каждого подпроекта. Lerna умеет решать данные проблемы.
Установка зависимостей:
lerna bootstrap
При выполнении данной команды Lerna анализирует все подпроекты и выполняет npm install в каждом из них. Если в зависимостях одного локального модуля (подпроекта) B находится другой A, то Lerna создает символическую ссылку в папке node_modules/ подпроекта B на подпроект A. Таким образом не происходит лишних копирований файлов в node_modules/. Работая с монорепозитоием, мы должны выполнять данную команду вместо установки зависимостей в каждом подпроекте.
Рекомендуем почитать в документации Lerna о дополнительных параметрах данной команды.
Автодеплой с помощью GitHub Actions
Мы создали монорепозиторий, даже смогли опубликовать его в NPM, но теперь возникает вопрос: "Как можно автоматически отправлять изменения в NPM?". Для решения такой задачи можно использовать GitHub Actions.
Создадим в корне проекта папку .github/workflows/, а в ней файл main.yml (название может быть любое). Рассмотрим на нашем примере.
Содержание нашего yml файла:
name: autodeploy on: push: branches: [main] pull_request: branches: [main] jobs: default: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v2 - name: Install Packages run: npm install - name: Authenticate run: | echo "@grandcore:registry=https://registry.npmjs.org/" > .npmrc echo "registry=https://registry.npmjs.org/" >> .npmrc echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc env: NPM_TOKEN: ${{ secrets.NPMTOKEN }} - name: Publish lerna publish from-package --yes --no-verify-access env: NPM_TOKEN: ${{ secrets.NPMTOKEN }}
Что тут происходит:
В качестве тригера запуска установлен коммит или пулл реквест в мастер;
Операционной системой выбирается Ubuntu;
Далее в склонированном репозитории выполняется установка npm-пакетов;
После этого добавляем пользователя NPM. Для этого необхдоимо создать секрет в проекте с npm-токеном. В нашем проекте токен содержится в секрете NPMTOKEN.
Выполняем публикацию с помощью соответствующей команды
lerna publish from-package --yes. В нашем случае, мы обновляем только те пакеты, которым мы поменяли вручную версию в их файлахpackage.json.Отслеживать ход выполнения можно во вкладке Actions в вашем репозитории.
Будем рады выслушать ваши замечания в комментариях.
Если читателя заинтересуют наши Open Source проекты, будем рады видеть в нашем чате.
