React Nativeアプリを含んだmonorepoにおいて、バージョン番号を上げる操作

# はじめに

開発が一区切りついてリリースする際に、バージョン番号を上げる操作を「bumpする」って呼んだりしますよね。番号を上げるだけと言えばだけなのですが、この記事では以下の様な観点に着目して、bumpの方法を考えてみます。

- monorepoのプロジェクトの場合、各パッケージのバージョンをさっと上げるには
- React Nativeアプリのパッケージを含んでいる場合、iOS/Android固有のファイルに書かれているバージョン番号を上げるには

JavaScriptのエコシステムにある色々なコマンドやライブラリを紹介していくのですが、色々整理した末に、「結局、このRuby gemでよくないですか・・・」というところに行き着く記事です。

# npmには、package.jsonのversionフィールドをbumpするコマンドがある

`npm version` というコマンドがあって、単体のpackage.jsonのbumpならばこのコマンドでできます。

https://docs.npmjs.com/cli/v7/commands/npm-version

```shell:使用例
npm version patch # パッチバージョンを上げる
npm version minor # マイナーバージョンを上げる
npm version major # メジャーバージョンを上げる
npm version 1.2.3 # 指定したバージョンにする
```

実行すると、package.jsonのversionフィールドが更新されます。

デフォルトではこの時、Gitにコミットしつつ、タグを打つところまでいっぺんに行われます。
そこまではしたくない場合もあるでしょう。 `--no-git-tag-version` オプションをつければ抑制できます。

```shell:オプションの使用例
npm --no-git-tag-version version patch
```

# Lernaを使っているmonorepoでは、Lernaのコマンドで子パッケージをbumpできる

monorepoのプロジェクトにおいては、子パッケージのpackage.jsonも一度にbumpしたい場面もあるでしょう。

[Lerna](https://github.com/lerna/lerna)を使っているプロジェクト固有の方法にはなるのですが、 `lerna version` というコマンドを使うと、子パッケージのbumpができます。
対象は子パッケージだけであり、ルートのpackage.jsonは変更されない点には注意です。

コマンドの使い方は `npm version` に似ています（詳細は[公式ドキュメントを参照](https://github.com/lerna/lerna/tree/main/commands/version#readme)）。

```shell:使用例
lerna version patch # パッチバージョンを上げる。同時にGitのコミットやタグ打ちもする。更に、pushもする。
lerna version patch --no-git-tag-version # npm versionのオプションと同様に、コミット以降を抑制する。

# lerna versionの実行時には、確認のプロンプトが表示される。--yesオプションをつけるとスキップできる。
lerna version patch --yes
lerna version patch --no-git-tag-version --yes
```

# React Nativeアプリでは、"react-native-version"を使うとiOS/Android固有のファイルをbumpできる

React Nativeアプリの場合、 `ios/` や `android/` に含まれるファイルの中にも、バージョン番号が書かれる箇所があります（Info.plistとか、app/build.gradleとか）。
`react-native-version` というライブラリを使うと、それらのファイルに対して、package.jsonにおけるversionの値を反映できます。

https://github.com/stovmascript/react-native-version

READMEに書かれている通り、下記の様な呼び出し方が典型的です。package.jsonのscriptsのところに `version` または `postversion` として指定します。

```json:package.jsonの抜粋
{
  "scripts": {
    "postversion": "react-native-version"
  }
}
```

`npm version` とセットで使うことを念頭に置かれているライブラリであり、デフォルトでは、直前のコミットにamendする形でInfo.plistなどが変更されます。 `--never-amend` オプションを使うと、amendはしないようにできます。

```shell
react-native-version --never-amend
```

# しかし、これらを組み合わせて使おうとすると、次第にややこしくなっていく

色々な道具が把握できてきたので、これらを組み合わせれば一通りのbumpが実現できそうです。しかし、細かい留意点が積み重なってきて、次第にややこしくなってきます。

1. lerna versionでは子パッケージしかbumpできない。
1. ルートパッケージのscriptsに `"postversion": "lerna version patch ...` って書こう。そして、ルートパッケージで `npm --no-git-tag-version version patch` って実行するようにしよう。
1. patchでなく、minorやmajorを上げたい時はどうする？

とか、

1. React Nativeアプリのパッケージにpostversionを書いて、react-native-versionを呼ぶようにしよう。これで、lerna versionによるbumpと繋がる。
1. なお、lerna versionのところで--no-git-tag-versionオプションを付けていると、各パッケージのpostversionもスキップされるらしい（[公式ドキュメント](https://github.com/lerna/lerna/tree/main/commands/version#lifecycle-scripts)）
1. そしたら、React Nativeアプリのパッケージに書くのは、postversionでなくversionにしておこう。

などなど・・・。

# 結局、このRuby gemに行き着いた

巡り巡って、以下のRuby gemがシンプルで分かりやすい気がしてきました。

- https://github.com/kt3k/ruby-bmp
- Denoに移植された版もあります。 https://github.com/kt3k/bmp

```shell:使い方
# gemをインストールする。
gem install bmp

# 以下のいずれかで実行する。
bmp -p|--patch
bmp -m|--minor
bmp -j|--major
```

以下の様な設定ファイルを用意しておくことで、bump対象を指定します。

```yaml:.bmp.yml
---
version: 1.0.0
files:
  lerna.json: '"version": "%.%.%"'
  package.json: '"version": "%.%.%"'
  packages/my-mobile-app/package.json: '"version": "%.%.%"'
  packages/my-mobile-app/android/app/build.gradle: versionName "%.%.%"
  packages/my-mobile-app/ios/MyMobileApp/Info.plist: "<string>%.%.%</string>"
```

Ruby製ツールの唐突な登場にはなりますが、これ単体で完結することと、設定ファイルを見ればbump対象が俯瞰しやすいことが利点です。必要な知識が少なく、READMEに「うちのプロジェクトではこのgemを使っています」と書いておけば、初見のメンバーもすぐキャッチアップできるでしょう。

npmやLernaのコマンドがあるだけに、それらを駆使して実現したくもなるのですが、Ruby製ツールを使うことに抵抗がなければ、十分おすすめです。