Link and Motivation Developers' Blog

リンクアンドモチベーションの開発者ブログです

LernaとGithub Actionsでmonorepoライブラリのリリースを楽にする

プラットフォームチームの菊池です。

弊社ではフロントエンドの共通コードを社内ライブラリにして開発しています。 https://link-and-motivation.hatenablog.com/entry/2022/08/10/152852

しかしライブラリのリリースが手動で行われていたため、問題が起きていました。 今回はリリース自動化にあたって、採用した方針と方法について紹介したいと思います。

背景

現状のリリースフローは以下のようになっていました。

  1. リリース作業者がローカルでビルドする
  2. リリース作業者がローカルでバージョンを打つ
  3. リリース作業者がローカルから社内npmレジストリGithub Packages)にpublishする

実際のコマンドとしてはこのような感じです。

# ビルドする
yarn build

# 新しいバージョンを打つ
yarn version

# npmレジストリに公開
yarn publish

npmスクリプトにまとめてはいたので、一見コマンドとしてはシンプルですが、下記の問題がありました。

  • 開発者の手元でビルドしているので、ビルド環境が不定(ローカル環境に依存する)
  • 開発者の手元でパブリッシュしているので、ビルドし忘れなどのミスが起きうる。また、npmレジストリへの認証を各作業者が行う必要がある

方針

この問題を解消するため、リリースの自動化に取り掛かりました。

ライブラリのリリースフローは、「次のversionを打つ」「buildする」「publishする」の作業に分解できます。

そして、それぞれ手元(ローカル)でやるか、CI/CD(リモート)でやるかの選択肢があります。

version

「versionを打つ」をリモートで行う場合、conventional commitsなどの一定のルールによって自動採番することになります。

完全自動化出来るので便利ですが、一方で柔軟(自由)にバージョンをつけるのが難しくなります。

たとえばまだ開発がFIXしてないけど、公開して試してみたい時にprerelease(e.g. v1.0.0-alpha.0)にしたい、というケースがある場合。 特殊なコミットルールに対応するよりも、シンプルに手元でprereleaseバージョンを付けられる方が簡単です。

まだ開発初期のライブラリであり、試しにリリースしたいケースが見込まれたため、「versionを打つ」はローカルでやる方式を優先しました。

build

次に「buildする」ですが、これをローカルでやると前述した問題「ビルド環境が開発者ローカルに依存する」にあたってしまうので、ここは確実にリモートで行う方式を取ります。

publish

最後に「publishする」ですが、ビルドをリモートで行う前提、ここはリモートで行うことになります。

結論

結果的に、以下を採用することにしました。

  • ローカルで「versionを打つ」
  • リモートで「buildする」
  • リモートで「publishする」

なお、いくつかmonorepoライブラリのリリース方法を参考に見ていたのですが、 例えばViteはこの形式でリリースしているようでした。

ツールの選定

この方針を実現するにあたって、いくつかツールを調査しました。

ツール versionを打つ publishする monorepo対応 概要
Release Please - リリースPRを自動作成してくれるツール
semantic-release - semverベースの自動化
semantic-release-monorepo semantic-releaseのmonorepo対応版
Ship.js - semverベースの自動化
Release It - semverベースの自動化
auto ラベルベースの自動化
changesets mdファイルベースの自動化
Lerna monorepo管理ツール

これらのツールはそれぞれ特徴があり、カテゴリとしても横並びに比較できるものではないので参考程度にご覧ください。

この中からmonorepo対応しているツールであること、方針のワークフローを採用できること、ライブラリとしての実績があることから Lernaを採用することにしました。*1

Lernaでは、まさに「ローカルでversionを打って、リモートでpublishする」ためのpublishオプション(lerna publish --from-git)があります。

(省略)This will identify packages tagged by lerna version and publish them to npm. This is useful in CI scenarios where you wish to manually increment versions, but have the package contents themselves consistently published by an automated process.

https://github.com/lerna/lerna/tree/main/commands/publish#bump-from-git

Lernaでタグのpushまで行ったら、Github Actionsでbuild -> publishを行います。

# .github/workflows/publish.yml 抜粋

on:
  push:
    tags:
      - '@lmi-mcs/*'

jobs:
  setup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - name: Install
        run: yarn --frozen-lockfile

  build:
    runs-on: ubuntu-latest
    needs: setup
    steps:
      - name: Run build
        run: yarn build

  publish:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Publish
        run: yarn lerna publish from-git --yes
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

改修後のフロー

改修後のフローをまとめると以下のようになります。

  • リリース作業者がローカルで、lerna versionでバージョンを付ける
    • タグがリモートにpushされる
    • !!!リリース作業者がやるのはここまで!!!
  • タグのpushを起点に、GitHub Actionsでbuild -> lerna publish --from-gitでnpm(GitHub Packages)にpublishする

lerna versionはプロンプトから好きなバージョンを選択出来るので、簡単に柔軟にバージョンを付けることが出来ます。

「もうちょっとバージョンルールを厳格にしたい」という場合はconventional commitsを利用したコミットからの自動採番も行えるので、Commitizenと併用して運用しても良いでしょう。

最後に

今回monorepoのリリース自動化を対応していて、思った以上にmonorepoに対応しているツールや事例が少ないな、、と感じました。 一度構築した後は、非常に便利に回せるようになりますが、結構つまづくポイントが多いですね。

というわけで、最後にmonorepo関連で非常に役立った記事やリンクを掲載しておきます。ぜひ参考にしてみて下さい。

参考

*1:Lernaは一時メンテナンスに入りましたが最近Nrwlに管理が引き継がれ5系になってwebサイトも新しくなりました。