カスタム JavaScript GitHub アクションを作成しよう

GitHub Actionsって便利ですよね😃

ちょっとした事でも自動化させたい時とかにめっちゃ便利です。

備忘録も兼ねてJavaScriptアクションの作り方を残しておきます。

## カスタムアクション

GitHubカスタムアクションとは、GitHub Actionsのワークフロー内で実行できるアクションを、独自作成したもののです。
例えばワークフロー中でリポジトリをチェックアウトするときに
```yml
      - name: Checkout
        uses: actions/checkout@v3
```
とよく書いたりしますが、これはactions/checkoutというv3タグの付いたバージョンのアクションを使用しています。

このアクションを独自に作成する。というのがカスタムアクションです。
そしてカスタムアクションには、以下の種類があります。

* Dockerコンテナ アクション
  - 環境を全てパッケージ化して一貫性と信頼性を提供
  - JavaScriptアクションより実行が遅くなる可能性がある
- JavaScript アクション
  - JavaScript アクションは、ランナーのマシンで直接実行可能であり、コードを実行環境から分離できるため、Dockerコンテナ アクションよりも迅速に動作する
  - パッケージ化された JavaScript アクションを作成するには、Node.js と npm のダウンロードが必要
  - より効率的にアクションを構築できるよう、GitHub Actions Toolkit Node.js の使用が推奨

* Composite アクション
  - Compositeは複数のワークフローを1つのアクションに集約 


詳細は以下のドキュメントを御覧ください。

https://docs.github.com/en/actions/creating-actions/about-custom-actions

## JavaScriptアクションの作成

今回は、プルリクエストが作成されたらコメントをGPT3.5で自動生成するアクションを作成してみます。
結果、以下のような感じになるものを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/846a7b8b-a86b-2268-ad27-c42de097fffc.png)

まずは全体の工程を知る為に以下のMicrosoft Learnを流してみるのが分かりやすくて良かったです。

https://learn.microsoft.com/ja-jp/training/modules/create-custom-github-actions/

カスタムアクションを作成する場合、まず `action.yml` というファイルを準備します。このファイルには、アクションのメタデータを記述します。

```yaml:action.yml
name: 'Generation Comments'
description: 'Generates and posts comments'
inputs:
  github-token:
    description: 'GitHub token for authentication'
    required: true
  openai-api-key:
    description: 'OpenAI API Key'
    required: true
  prompt:
    description: 'Custom prompt for GPT-3.5'
    required: true
    default: 'このプルリクエストに対して簡潔にコメントを返します。'
runs:
  using: 'node20'
  main: './dist/index.js'
```
`inputs`には処理の本体となる`index.js`に渡す引数を記述します。

`runs`に処理の本体である`index.js`のパスを記述します。
ここで1つポイントなのが、パスが`./dist/index.js`となっているところです。

一度、上述のMicrosoft Learnをやってみると分かるのですが、カスタムJavaScriptアクションでは実行に必要なパッケージもリポジトリに含める必要があります。

つまり`node_modules`のディレクトリも含める必要があるということです。
しかし、ここでは`vercel/ncc`を使って1つのJSファイルにコンパイルしたものを使用するようにしています。

https://github.com/vercel/ncc

そして本体の`index.js`は以下のようになりました。

```javascript:index.js
const core = require('@actions/core');
const github = require('@actions/github');
const openai = require("openai");

async function run() {
  try {
    const githubToken = core.getInput('github-token', { required: true});
    const openaiApiKey = core.getInput('openai-api-key', { required: true});
    const customPrompt = core.getInput('prompt', { required: true});

    const octokit = github.getOctokit(githubToken);
    const client = new openai.OpenAI({ apiKey: openaiApiKey });
    const context = github.context;
    const pull_number = context.payload.pull_request.number;
    const { data: pullRequest } = await octokit.rest.pulls.get({
      ...context.repo,
      pull_number,
    });
    
    const prompt = `##Order\n${customPrompt}\n##Pull Request Title: ${pullRequest.title}\n##Description: ${pullRequest.body}`;
    const completion = await client.chat.completions.create({
      model: "gpt-3.5-turbo",
      messages:  [{ role: "system", content: prompt}],
      max_tokens: 150
    });
    const generatedComment = completion.choices[0]?.message?.content;
    await octokit.rest.issues.createComment({
      ...context.repo,
      issue_number: pull_number,
      body: generatedComment,
    });
    console.log('Comment posted successfully');
  } catch (error) {
    core.setFailed(error.message);
  }
}

run();
```
入出力を取得したり、コメントを書き込んだり...などのパッケージは以下のツールキットから色々探すことができます。

https://github.com/actions/toolkit

前述の`vercel/ncc`を使ってコンパイルしておきます。
```bash
npx ncc build index.js -o dist
```
こういった階層になってます。
```
dist/
  └── index.js
.gitignore
README.md
action.yml
index.js
package-lock.json
package.json
```

コミット, プッシュしておきましょう。
```bash
git add .
git commit -m "first commit"
git tag v0
git push origin v0
git push origin main
```

## 実行してみる
作成したワークフローを別のGitHubレポジトリから使ってみます。
`workflow.yml`を以下のように記載しました。
```
.github
 └── workflows
     └── workflow.yml
```
```yaml
on:
  pull_request:
    types: [opened, reopened]

jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Sample Actions
        uses: <github-user-name>/<repo-name>@v0
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          openai-api-key: ${{ secrets.OPENAI_API_KEY }}
          prompt: 'このプルリクストを確認してユーザーの元気がでる一言を返してください'
```
`user`には`Githubユーザー名/レポジトリ名`@には付けたタグ名を記載します。

また、Settings -> Actions -> General から書き込み権限を付与しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/6f0987df-7a19-cbbb-b8ae-42fc63453703.png)

Secrets and variables -> Actions から`OPENAAI_API_KEY` をシークレットとして設定しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/2da15dee-be26-a962-775f-e962b3dac02c.png)

ここまでできたらプルリクエストを作成してワークフローを実行させてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/846a7b8b-a86b-2268-ad27-c42de097fffc.png)
と、上記のようにコメントが書き込まれました！

## さいごに
GitHub Actions便利ですね✨
今回はカスタムアクションに生成AIを簡単に組み合わせて使ってみましたが、アイディア次第で色々できそうです。