🦁

Rust で GitHub CLI extension を作る

2021/10/18に公開

少し前に GitHub CLI 2.0 がリリースされ、独自のカスタムコマンド(GitHub CLI extension)が作れるようになりました。
Zenn でも以下の方々が記事を書かれています。

https://zenn.dev/ymmmtym/articles/github-cli-gh-extension
https://zenn.dev/kawarimidoll/articles/75430b40622e7c

先人たちの GitHub CLI extension は以下から探せます。

https://github.com/topics/gh-extension

基本的にはシェルスクリプトで書くのが一番楽なのですが、自分は Rust で書きたいなと思ったのでそのやり方を紹介します。
説明するのは主にカスタムコマンドの実行ファイルの作り方とリリース方法についてです。

ちなみに今回作ったコマンドは以下です。
GitHub のデフォルトブランチ名を参照・変更することができます。

そろそろデフォルトブランチを master から main に変えようと思った時に、GUI からやるの面倒だから CLI でやりたいな、という自分のために作ったコマンドです。[1]

https://github.com/daido1976/gh-default-branch/tree/v0.1

やってることは gitgh コマンドを順番に実行してるだけなので、全然シェルスクリプトでよかったな、と途中で気づいてしまいましたが、この記事のネタになったのでよかったです。

作成方法

以下のリポジトリを参考にさせてもらいました。

https://github.com/vilmibm/gh-user-status/tree/v1.0.1
https://github.com/carlsberg/gh-releaser/tree/0.4.10

大まかな流れは以下です。

  1. Rust で何かしらの CLI アプリケーションを作る
  2. cargo build して生成したバイナリを gh release create でリリースする
  3. gh-xx の実行ファイル内で gh release download を行い、ダウンロードしたバイナリを exec コマンドで実行

みなさんお察しの通り、このやり方であれば Rust でなくともリリース用のバイナリを吐けるプログラミング言語なら応用可能です。
上記の参考リポジトリではそれぞれ Go、TypeScript(Deno) が使われています。

1. Rust で CLI アプリケーションを作る

ご自由にお作りください。
もちろんコード内で gh コマンドが利用可能です。
gh api コマンドの柔軟性が高いので、大体何でもできると思います。
あと、jq に依存せず --jq オプションが使えるのが素晴らしい です。

2. リリース

以下のようなリリース用スクリプトを書いて、$ ./release.sh v0.1 のような感じで実行します。

release.sh
#!/bin/bash
set -e

tag="${1}"
cargoOutput="./target/release/gh-default-branch"

if [ "${tag}" == "" ]; then
  echo "tag argument required"
  exit 1
fi

cargo build --release
mv "${cargoOutput}" "${cargoOutput}-${tag}"
gh release create "$tag" "${cargoOutput}-${tag}" --title="${tag}" --notes "${tag}"

3. コマンド実行(リリースしたバイナリのダウンロード)

gh コマンドに依存してますが、GitHub CLI extension を利用する = GitHub CLI をインストール済みということなので問題ありません。賢い解決方法ですね。

gh-default-branch
#!/bin/bash
set -e

# TODO: 現状だと新しいバージョンがリリースされるたびに手動でこの tag を書き換える必要があるので、
# バージョン文字列のみ別ファイルに逃し、リリース時に上書きするなどして自動で更新されるようにしたい
tag="v0.1"

repo="daido1976/gh-default-branch"
exe="gh-default-branch-${tag}"
extensionPath="$(dirname "$0")"

if [[ ! -x "${extensionPath}/bin/${exe}" ]]; then
  mkdir -p "${extensionPath}/bin"
  rm -f "${extensionPath}/bin/gh-default-branch-"*
  gh release -R"${repo}" download "${tag}" -p "${exe}" --dir="${extensionPath}/bin"
  chmod +x "${extensionPath}/bin/${exe}"
fi

exec "${extensionPath}/bin/${exe}" "$@"

参考

https://docs.github.com/en/github-cli/github-cli/creating-github-cli-extensions
https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository

脚注
  1. README に記載の通り、GUI からの rename に比べて圧倒的に機能が少ないのでご注意ください。 ↩︎

Discussion