KameWalk

pr-note という CLI ツールを作った

GitHub

pr-note という CLI ツールを作りました。
GitHub 上の 2 つのブランチを比較して、スカッシュマージも含めたマージされていない PR の情報を取得して概要を載せた PR を作成 or 更新するやつです。cargo, npm, homebrew でインストールできるので使ってみていただけると嬉しいです。

pr-noteのリポジトリ

既存のツールで参考にしたのは以下の 2 つです。やることはこちらのツールとだいたい同じです。

作った理由

やることがだいたい同じならなんで作ったのという話ですが、欲しい機能がなさそうだったからです。 2 つともリリース PR を作成してくれるのですが、個人的には PR をサクッとグルーピングできると嬉しいなと思っていました。

具体的には以下のようなイメージです。

僕が案件 A を担当していて、同じリポジトリで先輩が案件 B を、後輩が案件 C を担当しているとします。main ブランチにマージされた PR を定期的に release ブランチにマージしてリリースしています。そうすると main -> release に向けた以下のようなリリース PR ができることになります。

リリースPR(before)
- 案件AのPR1
- 案件AのPR2
- 案件BのPR1
- 案件CのPR1
- 案件BのPR2
- 案件Aのタイポ修正PR
- 案件Aのドキュメント修正PR
- 案件CのPR2
- 案件CのPR3

これが以下のようにグルーピングされているとかなり見やすいですね。

リリースPR(after)
## 案件A
- 案件AのPR1
- 案件AのPR2
- 案件Aのタイポ修正PR
- 案件Aのドキュメント修正PR
## 案件B
- 案件BのPR1
- 案件BのPR2
## 案件C
- 案件CのPR1
- 案件CのPR2
- 案件CのPR3

どちらも GitHub の API から返ってくるフィールドを持っていそうなので頑張ればいけそうですが、テンプレートの中で結構ゴリゴリやる必要がありそうだったので、自分で作ってみました。 他のツールだと release-please は微妙にやりたいことができなさそうかなという感じでした。

使い方

README に書いてあるまんまですが、必要最低限の指定としては以下のように実行します。他のオプションについては README を参照ください。

Terminal window
pr-note \
--owner octocat \
--repo Hello-World \
--base main \
--head feature \
--token <github_token>

pr-note では prs という変数にマージされていない PR の情報が配列で入っています。 prsは以下のような構造になっていて、テンプレートとなるファイル中でアクセスできます。

prs: [
{
"number": 123, // PR 番号
"author": "PRの作成者", // PR の作成者
"title": "PRのタイトル", // PR タイトル
"body": "PRのボディ", // マークダウン形式の PR ボディ
"labels": ["ラベル1", "ラベル2"], // PR に紐づいたラベル
"group": "グループ名" // 後述
},
...
]

テンプレートエンジンは Tera を使っているので、ドキュメントを参考に自分だけのテンプレを作ってみてください。Tera がかなり高機能なテンプレートエンジンなので、頑張ればかなり凝ったことが出来ると思います。ドライラン機能もあるので、手元にコマンドをインストールして--dry-runオプションをつけてテンプレの反映結果を見つつ調整すると良いです。

このツールのセールスポイントの 1 つであるgroup--group-byオプションでlabeltitleを指定した場合に設定されます。指定しない場合は空文字です。

labelを指定すると、PR に付与されているラベルが ”/” で結合されたものがgroupに入ります。
titleを指定すると、PR タイトルの先頭にある “[グループ名 1][グループ名 2]” のようにブラケットで囲まれた部分が ”/” で結合されたものがgroupに入ります。 labeltitleを指定したにも関わらず、グルーピングに利用できる情報がない(ラベルが付与されていない、ブラケットで囲まれた部分が存在しない)場合は “others” がgroupに入ります。

先ほど挙げた例をタイトルでグルーピングする際には、PR タイトルにブラケットをつけることで機能するので、以下がより実際に近いイメージになります。

リリースPR(after2)
## 案件A
- [案件A]PR1
- [案件A]PR2
- [案件A]タイポ修正PR
- [案件A]ドキュメント修正PR
## 案件B
- [案件B]PR1
- [案件B]PR2
## 案件C
- [案件C]PR1
- [案件C]PR2
- [案件C]PR3

グルーピングの対象としてはラベルで十分な気もしますが、プロジェクトによってはラベルを使わない場合もあると思ったので両方用意しました。

もしも案件ごとにラベルを作成していくとなるとラベルの管理が大変ですし、ラベルを使わないプロジェクトの場合はそもそもラベルが存在しないので、手軽に使えるタイトルベースのグルーピングが便利かなと思います。チケットで案件を管理するというケースも多いと思うので、案件のチケット番号をブラケットに含めるようにすれば自然とグルーピングできて良さげです。

また、PR 側のボディをマークダウンで取得するため、その中身に応じて条件分岐とかもかなり実用的だと思っています。 PR 側 のテンプレにチェックリストを用意しておいて、そのチェックが入っている場合にはリリース PR 側でもチェックを入れる、みたいなこともできます(pr-note のデフォルトのテンプレでやっています)。

リリースPR(after3)
## 案件A
- [x] [案件A]PR1 <!-- PR側でチェックが入っているのでリリースPRでもチェックされる -->
- [x] [案件A]PR2
- [x] [案件A]タイポ修正PR
- [x] [案件A]ドキュメント修正PR
## 案件B
- [ ] [案件B]PR1 <!-- PR側でチェックが入っていないのでリリースPRはチェックされない -->
- [ ] [案件B]PR2
## 案件C
- [x] [案件C]PR1
- [x] [案件C]PR2
- [x] [案件C]PR3

実際に仕事でもこの案件の PR はリリースしていいのかな?みたいな確認をチェックリストでやっているので、そのままリリース PR にも反映できるのはかなり便利そうな気がしています。

頑張りポイント

pr-note は Rust で作ったのですが、自分が仕事で使っているのは主に TypeScript なので npm パッケージとしても使えるようにしました。github-actions 上でビルドして、各プラットフォーム向けのバイナリをそれぞれ作成して公開しています。現在は以下のプラットフォーム向けに公開しています。

ただ、実際に npm パッケージとしてインストールする際にはnpm install -D @pr-note/pr-noteのようにすれば、プラットフォームに応じたバイナリがインストールされるのであんまり気にしなくて良いです。

最初から JavaScript(TypeScript) で作っておけばプラットフォームごとの違いなんか気にしなくて済んだということを粗方作り終えたあとで気づいたのですが、まぁ勉強になったよねという言葉で自分を慰めています。
Rust で作成したツールを各プラットフォーム向けの npm パッケージとして公開する方法については、ふくださんの Rust 製 CLI ツールを npm で公開・配布するという記事と、公開してくださっているツールが参考になりました。とてもありがたいです。

実装面では GitHub の GraphQL API を使って PR の情報を取得しており、一発でほしい情報が取れるのは便利なのですが、ネストが深く返ってくるのでパースするのが少し面倒でした。
ちなみに、コミットに紐づく PR の情報を取得するために associatedPullRequests というフィールドを参照しているのですが、これはスカッシュマージも含む紐づいた PR を取得できるので便利です。このフィールドが REST API にはなさそうだったので GraphQL API を使うことにしました。 なぜかはわからないので、気になる方は GitHub に聞いてみてください。

残念ポイント

リリースのやり方がかなり雑になってしまいました。最初の 0.1.0 のリリースで crates と npm の両方にリリースしようとしたところ、crates にはリリースできたのに、npm にはリリースできませんでした。頑張って色々やりましたが npm 側の最初のリリースが 0.1.0 ではなく 0.1.1 になってしまいました。さらに、わちゃわちゃしているうちに crates 側のバージョンも 0.1.1 をすっ飛ばしてしまいました。

今後新しいツールを作るときには安全第一でやっていきたいと思います。

また、テストを全然書いていないのでバグまみれの可能性があります。ただ、最低限は動くのでヨシとしています。ちょこちょこ書き足していく予定です。

実装的な制限としては、取得するコミットの数を最新 200 件とラベルの数を 5 件に制限している状態なので、だいたいのケースでは十分な気がしますが、超巨大なリリース PR やラベルが大量についているパターンには対応できていません。
手元で試した感じだと、コミットの数を 250 件より増やすとレスポンスが返ってこなくなったので、200 件に抑えています。ラベルについては適当です。コミットの数についてはページネーションを実装すれば対応できそうですが、面倒そうなのでやっていません。 ただ、自分の利用範囲ではこれで十分なので、今後必要になったら対応することを検討する方向で調整できるよう善処したいです。

まとめ

はじめて自分でツールを作って広く利用できる形で公開するというところまでやってみたのですが、想像よりもかなり大変でした。先述の git-pr-release や github-pr-release をはじめ、OSS を作成・公開してくださっている多くの方には改めて感謝感激雨霰という感じです。大変さが理解できた分、ありがたみも一入です。

また、このツールを作成するにあたり多大な助力をしてくれた ChatGPT にも感謝します。
時々とんでもない嘘をついて惑わせてくれたりもしましたが、全体としてはかなり助けられました。

欲しい機能は大方実装できたので、あとはテストやドキュメントの充実を図っていきたいと思います。

参考