gemのリリースを自動化する

Posted by Shugo Maeda on June 04, 2025 · 3 mins read

NaClの前田です。

筆者が開発しているTextbringerというgemのリリースを rake bump コマンド一発でできるように先日自動化したのですが、少しはまったポイントがあったので記事にまとめます。

前提

Textbringerの開発はGitHubで行っているのですが、Trusted Publishingという仕組みでGitHub Actionsでgemのリリースを行うことができるため、もともとタグをpushした時にリリースできるようにしていました。

ただ、バージョン番号を上げるpull requestを作成して、マージ後にタグを作成してpushをする作業を毎回手でやるのが面倒になってきたため、手元で rake bump コマンドを実行するだけでバージョンを上げてリリースできるようにしました。

rake bumpの実装

lib/textbringer/version.rbに記載されているTextbringer::VERSIONをインクリメントして、GitHubへのpushとpull requestの作成を行うようにしました。

task :bump do
  require_relative "lib/textbringer/version"
  version = Textbringer::VERSION.to_i + 1
  puts "Bump version to #{version}"
  system "git checkout main" or exit(1)
  system "git pull" or exit(1)
  system "git checkout -b bump_version_to_v#{version}" or exit(1)
  File.write("lib/textbringer/version.rb", <<~EOF)
    module Textbringer
      VERSION = "#{version}"
    end
  EOF
  system "git commit -a -m 'Bump version to #{version}'" or exit(1)
  system "git push" or exit(1)
  system "gh pr create --title 'Bump version to #{version}' --body ''" or exit(1)
  system "git checkout main" or exit(1)
end

version = Textbringer::VERSION.to_i + 1 というコードを見て大丈夫か不安になる方もいるかもしれませんが、TextbringerはLatestVerというバージョニングポリシーを採用しており、バージョン番号は1桁なので問題ありません。

GitHub Actionsのワークフロー(失敗例)

当初は以下のようなワークフロー(.github/workflows/tag_version.yml)を追加し、lib/textbringer/version.rbの変更がmainに入ったら v6 のようなタグを作成するようにしました。

name: Tag version

on:
  push:
    branches:
      - main
    paths:
      - 'lib/textbringer/version.rb'

permissions:
  contents: write

jobs:
  tag_version:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: k1LoW/github-script-ruby@v2
        with:
          script: |
            require "./lib/textbringer/version"
            repo = "#{context.repo.owner}/#{context.repo.repo}"
            tag = "v#{Textbringer::VERSION}"
            github.create_ref(repo, "refs/tags/#{tag}", context.sha)

しかし、この方法ではタグは作成されるものの、肝心のTrusted Publishingのワークフローが実行されませんでした。
どうもGitHub Actionsではループを避けるためにワークフロー中でリポジトリが変更された場合は他のワークフローがトリガーされないようになっているようです。
personal access tokenを使ったり、GitHub App化すれば他のワークフローをトリガーできるようですが、面倒なので他の方法を取ることにしました。

GitHub Actionsのワークフロー(成功例)

すべての処理を一つのワークフローで行ってしまえば他のワークフローをトリガーする必要はないので、 bump_version_to_v* というブランチが作成された時に以下の処理を実行してしまうことにしました。

  1. ブランチのオートマージ (gh pr merge --merge --auto "$PR_URL")
  2. タグの作成 (git tag "${tag_name}")
  3. gemのリリース (uses: rubygems/release-gem@v1)

1はあらかじめオートマージできるようにリポジトリを設定しておく必要があります。
本当はmainにマージされてからタグを作成してリリースするようにしたかったのですが、1の処理はマージが完了する前に返ってきてしまうので、しかたなくブランチでタグを作成してリリースするようにしました。
version.rbの変更だけでテストが通らなくなることは考えにくいので実用上は問題ないと思います。

修正後のワークフローは以下の通りです。

まとめ

  • 当初の目論見通りgemのリリースを自動化できました。
  • GitHub Actionsではpersonal access tokenを使うなどしないと他のワークフローをトリガーできないので注意が必要です。
  • バージョン番号なんて飾りです。偉い人にはそれがわからんのです。

再修正(2025-06-05追記)

上記でうまくいったと思ったのですが、pull requestのブランチ上でタグを作成したことで、pull requestのマージ後にブランチが削除されてタグがリポジトリ上のどのブランチにも所属しない状態になり、そのせいかGitHub Releasesのドラフトを作成する時に直前のバージョンからの差分ではなく最後にmainからタグを作成してリリースしたバージョンからの差分になってしまったため、GitHub Actions上でのタグ作成はあきらめてローカルで作成するようにしました

task :bump do
  require_relative "lib/textbringer/version"
  version = Textbringer::VERSION.to_i + 1
  tag_name = "v#{version}"
  puts "Bump version to #{version}"
  sh "git checkout main"
  sh "git pull"
  File.write("lib/textbringer/version.rb", <<~EOF)
    module Textbringer
      VERSION = "#{version}"
    end
  EOF
  sh "git commit -a -m 'Bump version to #{version}'"
  sh "git push"
  sh "git tag #{tag_name}"
  sh "git push origin #{tag_name}"
end

もともとpull requestにしていたのはオートマージを有効にしてmainへのpushを禁止していたからなのですが、RulesetのBypass listにRepository adminを追加して自分はmainにpushできるようにしてしまいました:(