虎の穴ラボ技術ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

「PRコメントのAI要約」を例に学ぶ、GitHub Actionsカスタムアクション開発

こんにちは。はっとりです。

皆さんは普段、GitHub Actionsを使ってCI/CDや定型作業の自動化を行っているかと思います。既存のアクションを組み合わせるだけでも非常に強力ですが、「こういうことができたら便利なのに…」と感じることはないでしょうか。

GitHub Actionsのカスタムアクションは意外と簡単に作れます。

この記事では、その一例として「PRのレビューコメントをAIで要約するアクション」を題材に、カスタムアクション開発の基本的な流れと、気づきにくいポイントを紹介します。

GitHub Actionsの作り方について

公式に説明があります。
カスタム アクションについて - GitHub Docs
JavaScript、Dockerコンテナ、複合アクションなどいくつか作る方法はありますが、今回はJavaScriptで作る方法を選択しました。

まずは完成形:こんなものが作れます

今回サンプルとして作成したのは、PRに付いたレビューコメントをAIで要約し、TODOチェックリストとしてPR本文に自動で書き出してくれるアクションです。

全ソースコードはこちらのリポジトリで公開していますので、ぜひ参考にしてみてください。 github.com

https://raw.githubusercontent.com/boushi-bird/pr-review-digest/refs/heads/main/sample.png

このように、GitHub上の特定のイベントをトリガーに、独自のロジックを動かすことができます。
では、このようなアクションをどうやって作るのか、具体的なステップを見ていきましょう。

カスタムアクション開発の3ステップ

カスタムアクションは、大きく分けて3つの要素で構成されています。

  • package.json
    • プロジェクトの依存関係を管理
  • action.yml
    • アクションのメタデータ(入力、名前など)を定義
  • lib/index.js
    • アクション本体の処理を記述。今回はsrc/index.tsにTypeScriptで記述し、依存ライブラリとバンドルして生成。

この3つを用意するのが基本の流れです。

ステップ1: プロジェクトの準備

まずは、通常のNode.jsプロジェクトと同じように初期化します。

npm init -y
npm install @actions/core @actions/github
npm install -D @vercel/ncc

TypeScriptを使う場合はその設定も必要になります。(JavaScriptで記述する場合は不要)

npm install -D typescript @types/node
npx tsc --init

tsconfig.json には、ソースコードの場所と出力先を指定するために以下を追加します。

+    "rootDir": "./src",
+    "outDir": "./lib",

また、 verbatimModuleSyntaxfalse または 設定から削除します。TypeScriptの話になってしまうので詳しい説明は省略しますが、nccを使う際はこの設定を無効にする必要があります。

-   "verbatimModuleSyntax": true,

一旦この設定で使えますが、必要に応じて設定を追加・変更してください。※例えばソースマップ関連は出力しないなど

利用したライブラリについても軽く説明します。これらは必須ではありませんがあると作成が非常に簡単になります。

  • @actions/core
    • ワークフローからの入力取得や、ログ出力、失敗通知など、アクションの基本的な機能を提供してくれるライブラリです。
  • @actions/github
    • Octokit(GitHub APIクライアント)や、PR番号・リポジトリ名といった実行コンテキストへのアクセスを簡単にしてくれます。
  • @vercel/ncc
    • TypeScriptのコードと、node_modules内の依存関係をすべて1つのJavaScriptファイルにコンパイルしてくれるツールです。トランスパイルとバンドルさえできればviteやwebpackなどでも問題ないです。今回のケースでは設定不要で非常に簡単に扱えるためnccを選びました。

package.jsonには、nccを使ったビルドスクリプトを定義しておきましょう。

// package.json
"scripts": {
  "build": "ncc build src/index.ts -o lib"
},

ステップ2: アクションを定義するaction.ymlを作る

action.ymlは、このアクションがどのようなもので、何を受け取り、どう実行されるのかを定義する設定ファイルです。

# action.yml
name: 'PR Review Digest'
description: 'このGitHub Actionは、プルリクエスト(PR)に投稿されたレビューコメントやIssueコメントをOpenAI APIを利用して自動的に要約し、その結果をPRの本文にチェックリストとして挿入・更新します。'

# このアクションが受け取るパラメータ(入力)
inputs:
  token:
    description: 'GitHub API認証用のトークン。通常は設定する必要はありません。'
    required: false
    default: ${{ github.token }}
  openai-api-key:
    description: 'OpenAI APIキー。リポジトリのSecretsに設定することを強く推奨します。'
    required: true
  openai-model:
    description: '要約に使用するOpenAIのモデル名。'
    required: false
    default: 'o3-mini'

# 実行環境とエントリーポイントの指定
runs:
  using: 'node24'
  main: 'lib/index.js' # nccでビルドされたファイル
  • inputs
    • ワークフローのwith:句から渡されるパラメータを定義します。required: trueで必須項目にしたり、defaultでデフォルト値を設定できます。
  • runs
    • using: 'node24'でNode.js環境で動かすことを宣言し、main:でエントリーポイントとなるファイル(nccでビルドされた後のファイル)を指定します。

たったこれだけで、アクションの定義は完了です。

ステップ3: メインの処理を書く

いよいよ本体のロジックです。先ほど紹介した@actionsライブラリのおかげで、非常にシンプルに記述できます。

// src/index.ts
import * as core from "@actions/core";
import * as github from "@actions/github";

async function run() {
  try {
    // 1. action.ymlで定義した入力を受け取る
    const openaiApiKey = core.getInput("openai-api-key", { required: true });
    const model = core.getInput("openai-model"); // デフォルト値があるのでrequiredは不要

    core.info(`Using model: ${model}`); // ワークフローのログに出力

    // 2. GitHubのコンテキスト情報や認証済みAPIクライアントを取得
    const token = core.getInput('token'); // デフォルトで `github.token` が入る
    const octokit = github.getOctokit(token);
    const { owner, repo } = github.context.repo;
    const prNumber = github.context.payload.pull_request?.number;

    if (!prNumber) {
      core.setFailed("このアクションはプルリクエストで実行する必要があります。");
      return;
    }

    // 3. やりたい処理を自由に実装する
    // (例: 今回はPRのコメントを取得し、OpenAI APIに渡して要約する処理...)
    // const comments = await octokit.rest.pulls.listReviews(...);
    // const summary = await callOpenAI(comments);
    // await octokit.rest.pulls.update({ body: summary });

    core.info("アクションが正常に完了しました。");

  } catch (error) {
    // 4. エラーが発生したらアクションを失敗させる
    core.setFailed((error as Error).message);
  }
}

run();

ポイント:

  • core.getInput()で、action.ymlで定義した入力を簡単に受け取れます。
  • github.getOctokit()github.contextを使えば、API認証やリポジトリ情報などを自分で取得する必要がなく、すぐにメインの処理に取り掛かれます。
  • core.info()core.setFailed()で、ワークフローの実行結果を適切にハンドリングできます。

あとは、// 3. やりたい処理 の部分に、今回で言えば「OpenAI APIを叩いてPR本文を更新する」といった、本当にやりたいロジックを実装するだけです。

作ったアクションを動かしてみる

自作したアクションは、同じリポジトリ内のワークフローから簡単に呼び出せます。 uses:に公開済みのアクション名を指定する代わりに、./(リポジトリのルート)を指定するだけです。

# .github/workflows/test.yml

on:
  pull_request_review:
  pull_request_review_comment:
    types: [edited, deleted]
  pull_request:

jobs:
  test-custom-action:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      # npm install や build を実行
      - name: Build action
        run: npm install && npm run build

      - name: Run my custom action
        uses: ./ # ここで自作アクションを指定!
        with:
          openai-api-key: ${{ secrets.OPENAI_API_KEY }}

これで、プッシュやPR作成をトリガーにして、自作アクションの動作確認ができます。

最後に: リリースする

作成したアクションを他のリポジトリからでも利用できるように、タグを打ってリリースしましょう。

また、ビルドなどは自動でやってくれないため、npm run buildした結果自体もリポジトリに含める必要があります。

git add lib/index.js
git commit -m "(コメント)"
git push origin (デフォルトブランチ)

git tag v1.0.0
git push origin v1.0.0

他のリポジトリで使うには以下のように指定するだけで利用可能になります。

# Action名
name: Summarize PR Comments

# トリガーとするイベント
on:
  pull_request_review:
  pull_request_review_comment:
    types: [edited, deleted]

jobs:
  summarize:
    runs-on: ubuntu-latest
    # 必要に応じてパーミッションも必要になる
    permissions:
      pull-requests: write
    steps:
      - name: Summarize PR Comments
        uses: boushi-bird/pr-review-digest@v1.0.0 # (organization or user)/(リポジトリ名)@(タグ名)
        # 利用するのに必要な入力を追加
        with:
          openai-api-key: ${{ secrets.OPENAI_API_KEY }}
          openai-model: 'o3-mini'

READMEにこの設定例も書いておくと、他の人が使うときにとても親切です。

まとめ

見てきたように、GitHub Actionsのカスタムアクション開発は、

  1. いくつかの便利ライブラリ (@actions/core, @vercel/nccなど) を導入する
  2. action.ymlで入力を定義する
  3. 処理を書く。

というシンプルなステップで構成されており、一度流れを掴めば決して難しくありません。

日々の開発で発生する「ちょっとした面倒事」や「繰り返し行っている手作業」は、カスタムアクションを作ることで解決できるかもしれません。 皆さんもぜひ、自分だけの便利なアクション作りに挑戦してみてはいかがでしょうか。