gh-attach: GitHub Issue/PRに画像を自動アップロードするCLIツールを作った

ツール

はじめに

CI/CDでE2Eテストの結果スクリーンショットをGitHub Issueに自動投稿したい。

そんな単純な要件なのに、実現しようとすると意外な壁にぶつかりました。GitHubにはIssueコメントに画像をアップロードするAPIが存在しないのです。

Web UIでは普通にドラッグ&ドロップで画像を貼り付けられるのに、APIからはできない。この制限を補うために作ったのが gh-attach です。

作ったもの

gh-attach は、GitHub Issue/PRのコメントに画像をアップロードするCLIツールです。

# 基本的な使い方
gh-attach --issue 123 --image ./screenshot.png

# コメント本文と一緒に
gh-attach --issue 123 --image ./e2e.png --body "E2E test result:"

# 複数画像をアップロード
gh-attach --issue 123 
  --image ./before.png 
  --image ./after.png 
  --body 'Before: <!-- gh-attach:IMAGE:1 -->
After: <!-- gh-attach:IMAGE:2 -->'

なぜAPIで画像をアップロードできないのか

GitHubのIssueコメントAPIは body パラメータにMarkdownテキストを受け取るだけで、ファイルをアップロードする機能がありません。

Web UIで画像を貼り付けると https://user-images.githubusercontent.com/... のようなURLに変換されますが、このアップロード先 uploads.github.com はOAuthトークンでの認証を受け付けず、ブラウザのセッションCookieが必要です。

つまり、公式にはAPI経由での画像アップロードはサポートされていないのです。

3つのアップロードモード

gh-attach には用途に応じた3つのアップロードモードがあります。

Browser mode(デフォルト)

APIがダメならブラウザを使えばいい。playwright-cli でブラウザを自動操作する方法です。

  1. プレースホルダー付きのコメントをAPIで作成
  2. playwright-cliでGitHubをブラウザで開く
  3. コメントの編集画面で画像をアップロード
  4. アップロードされた画像URLを取得
  5. APIでコメントを更新し、プレースホルダーを画像に置換

ヘッドレスモードで動作するため、CI環境でも使えます。初回のみ --headed オプションでブラウザを表示してGitHubにログインすれば、以降はセッションが保持されます。

Release mode(--release)

ブラウザ不要で動作するモードです。GitHub Releasesに画像をアップロードし、そのダウンロードURLを使います。

gh-attach --issue 123 --image ./screenshot.png --release

playwright-cliのセットアップが不要なため、CI環境での導入が手軽です。

Direct mode(GHE向け)

GitHub Enterpriseの upload/policies APIを利用するモードです。ブラウザでポリシーを取得し、curlでファイルをアップロードすることで user-attachments URLを生成します。

設定ファイル(~/.config/gh-attach/config)に対象ホストを登録すると自動で有効になります。

direct_hosts=your-ghe-host.com
gh-attach --issue 123 --image ./screenshot.png --host your-ghe-host.com --repo owner/repo

Release modeと違い、リリースアセットを汚さずに user-attachments URLが得られるのが利点です。

技術的なポイント

プレースホルダーによる画像位置の制御

複数画像をアップロードする際、どこに画像を挿入するかを制御できるようにしました。

テスト結果:
<!-- gh-attach:IMAGE:1 -->

修正後:
<!-- gh-attach:IMAGE:2 -->

プレースホルダーがない場合は、コメントの末尾に画像が追加されます。

GitHub Enterpriseサポート

--host オプションでGitHub Enterpriseにも対応しています。ホストを指定しない場合は、現在のリポジトリの設定から自動検出します。

gh-attach --issue 123 --image ./result.png --host github.mycompany.com

画像サイズの固定

アップロードされた画像は <img> タグで挿入され、デフォルトで幅800pxに固定されます。--width オプションで変更可能です。

Direct mode の仕組み

Direct modeは、GitHubのファイルアップロードの内部APIを逆解析して実現しました。

  1. playwright-cliでIssueページを開く
  2. file-attachment Web Componentの attach() を呼び出してアップロードポリシーを取得
  3. window.fetch をインターセプトしてポリシーデータをキャプチャ
  4. XMLHttpRequest.prototype.send をブロックして、コンポーネントによるアップロード実行を防止
  5. 取得したポリシー情報(URL、認証ヘッダー、フォームデータ)を使い、curlで実ファイルをアップロード
  6. レスポンスから user-attachments URLを取得

ブラウザ内のJavaScript実行とcurlを組み合わせたハイブリッドなアプローチです。

インストール

Homebrewでインストールできます。

brew tap atani/tap
brew install gh-attach

# 初回はブラウザでGitHubにログイン
gh-attach --issue 1 --image ./test.png --headed

gh extensionとしてもインストールできます。

gh extension install atani/gh-attach
gh attach --issue 123 --image ./screenshot.png

おわりに

「APIにない機能はブラウザ自動化で補完する」という力技ですが、実用上は問題なく動作しています。

ブラウザ自動化は自身のアカウントでの操作に限定し、過度なリクエストを避けるなど、GitHubの利用規約に配慮した使い方を心がけてください。

CI/CDでのスクリーンショット投稿、テスト結果の可視化など、自動化の幅が広がるツールになりました。

リンク

タイトルとURLをコピーしました