Привет! Мы 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 проекты, будем рады видеть в нашем чате.

Комментарии (16)


  1. Oxyd
    30.09.2021 23:26
    -1

    Я правильно понимаю, что это только для проектов на js или?...


    1. Shatun
      30.09.2021 23:44

      Да, только для js(ts). Честно говоря немогу представить зачем вам бы понадобилась лерна для других языков-там свои аналоги и части проблем которые есть в js нету(зато есть свои)


    1. Pugavkomm
      01.10.2021 09:10

      Lerna - это инструмент, который оптимизирует рабочий процесс по управлению репозиториями с несколькими пакетами с помощью git и npm.


      1. Oxyd
        04.10.2021 05:27

        Ясно, спасибо.


  1. MarkFish
    01.10.2021 01:37
    -1

    Последний раз когда читал про монорепозитории, писали, что проблем с ними дофика. Особенно с opensource проектами (историю коммитов рвет, чего-то там с отправкой pull reques и т.д.).


    1. grandcore Автор
      01.10.2021 11:02
      +1

      А что делать, если куча либ на две строчки? Репозитории плодить?


    1. rock
      01.10.2021 11:09

      Вы это себе как представляете? С точки зрения системы контроля версий, это обычный репозиторий, только в нем несколько пакетов, а инструменты вроде lerna просто упрощают управление этими пакетами (связывание, публикация и т.п.) - так что это не тот уровень, на каком могут возникнуть упомянутые вами проблемы.


    1. Shatun
      01.10.2021 13:39

      Последний раз когда читал про монорепозитории, писали, что проблем с ними дофика. Особенно с opensource проектами (историю коммитов рвет, чего-то там с отправкой pull reques и т.д.).

      А это несовсем монорепозиторий в том смысле в котором обычно имеется ввиду. Монорепо с лерной - это чаще просто репа, в которой хранится несколько проектов. С точки зрения гита, туллинга это одна гит репа(либ гит модули)


      1. MarkFish
        01.10.2021 13:46

        Монорепо с лерной - это чаще просто репа, в которой хранится несколько проектов.

        Я с ними не работал, читал только, так что ничего не утверждаю.

        Люди писали, что при публикации как раз таки проекта хранащегося в монорепе, та же история его коммитов будет оборвана.


        1. Pugavkomm
          01.10.2021 17:21
          +1

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


  1. Ustas4
    01.10.2021 04:09

    Публикация изменённых проектов:

    lenra publish

    Это опечатка? Lenra?


    1. grandcore Автор
      01.10.2021 04:09

      Спасибо.


  1. DuD
    01.10.2021 04:28

    Есть ли в lerna способ задать ту версию которую я хочу выставить? По дефолту она вроде сама инкрементит версию, но как быть если версия приходит "извне" и не поддается простой логике lerna?


    1. grandcore Автор
      01.10.2021 09:09

      Смотри вариант в нашем проекте который мы сделали. Там по изменению версии в пэкеджджэйсоне как раз автоматически пушится на нпм всё. Если версию не менять - экшен просто в холостую пробегается.


  1. Lodin
    02.10.2021 15:49

    1) А как у вас npm install без actions/setup-node работает?

    2) Если использовать actions/setup-node, то необходимости вызывать команду echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrcнет, т.к. экшн это делает по-умолчанию. Разве что переменная называется NODE_AUTH_TOKEN.


  1. grandcore Автор
    08.10.2021 14:50

    Там была ошибка - http вместо https. Поправили конфиг гитхаб экшена.