GitHubコメントを実行してあらゆることを自動化するGitHub Actions

## やりたいこと
GitHubのissueやプルリクエストのコメントに任意のスクリプトを書いてGitHub Actionsで実行したいです。

これができると色々と自動化できます。

例えば、

- プルリクをマージした時のプレビュー
- npmのパッケージをアップデートして自動でプルリク
- SSHでGitHub Actions内に入ってデバッグ
- Python/Ruby/Go/Deno/Scala .... 任意言語の実行
- LGTMの画像をコメント
- ...
などなどアイデア次第でGitHubコメントする手軽さであらゆることを自動化できます。
`npm`でも`apt`でも`pip`でも`git`でも色々コメントからGitHub完結で実行できます。


## できたもの

comment-runと呼んでます。
GitHub: <https://github.com/nwtgck/actions-comment-run>
上記のREADMEにここで紹介するいくつかの実行可能なコメントを載せています。コピペしたい時に便利です。

## 自分のリポジトリにcomment-runを導入する

`.github/workflows/comment-run.yml`として以下を設置するだけです。

```yaml:.github/workflows/comment-run.yml

name: "Comment run"
on:
  issue_comment:
    types: [created, edited]

jobs:
  comment-run:
    runs-on: ubuntu-18.04
    steps:
    - uses: actions/checkout@v2
      with:
        # 0 indicates all history
        fetch-depth: 0
    - uses: nwtgck/actions-comment-run@v1.0
      with:
        github-token: ${{ secrets.GITHUB_TOKEN }}
        allowed-associations: '["OWNER"]' 
```

以下でコマンドをリポジトリのルートで叩けば一発で導入できます。

```bash
mkdir -p .github/workflows/ && cd .github/workflows/ && wget https://gist.githubusercontent.com/nwtgck/a9b291f6869db42ecc3d9e30d0a0494c/raw/comment-run.yml && cd -
```


pushすれば自動導入されます。GitHub側で設定することは特にないです。

## comment-runを使ってみる

まず新しくissueを立てます。（既存のissueでもOKです。）
その中で以下のようにコメントしてみます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/51ccf6cc-99d4-dc45-1768-57d98fd20444.png" width="700">

以下はコピペ用です。書き始めを`@github-actions run`にします。```` ```js ````のコードブロック内が実行できます。

> @github-actions run
> 
> \`\`\`js
> console.log("hello, world");
> \`\`\`

つぎに以下のActionsタブを開いて、
<img width="604" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/d57d78f5-eaed-e768-08aa-6c590766f7b2.png">

以下のように"hello, world"が出ているのがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/ba070647-d6a7-2d50-0926-11c5ccf9054c.png)

## LGTM画像をコメントしよう

もう少し使えそうな使用例の紹介です。
以下のコメントでLGTM.in/gからランダムで画像がコメントされます。
<img width="759" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/16c55f61-8846-d01f-69fd-2f5a52eca9a4.png">
実際のコメント：<https://github.com/nwtgck/actions-comment-run/pull/1#issuecomment-596302174>
コメント内で`details`タグを使えばすっきりと自動化のコメントを投稿できます。

## よく使うコメントをGitHubに覚えさせる

GitHubにはSaved repliesという機能があり（@peaceiris氏に教えてもらいました）、
これでcomment-run用のコメントを覚えさせると自動化が捗ります。

Saved repliesの記憶のさせ方は、
まず以下のSettingsに行きます。
<img width="231" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/2ab553a0-405b-827d-4323-79572a505d35.png">

そして、Saved repliesを選んで記憶させたいものを記入できます。
<img width="566" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/a4f0d67d-68b6-3ebf-5655-ae6256e9fb3f.png">

覚えさせたコメントを使いたいときは、
以下のコメント投稿欄の右上にある「↖️」アイコンを押すと選べます。
<img width="690" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/ad95d964-1589-3e1c-2095-94f167cb67e6.png">


## npmパッケージを更新して自動でプルリクを作る

使用例です。
Dependabotは便利ですが、大量に更新があるときにまとめて更新してくれると助かることがあります。更新の自動プルリク作成がGitHub完結で行えます。
以下のマークダウンのコメントを投稿するだけです。

<pre>
<code>
@github-actions run

```js
function exec(cmd) {
  console.log(execSync(cmd).toString());
}

// Config
const baseBranchName = context.payload.repository.default_branch;
const gitUserEmail = "github-actions[bot]@users.noreply.github.com";
const gitUserName = "github-actions[bot]";
const prBranchName = "comment-run/npm-update";

exec(`git config --global user.email "${gitUserEmail}"`);
exec(`git config --global user.name "${gitUserName}"`);
exec(`git fetch --all`);
exec(`git checkout ${baseBranchName}`);
exec(`git checkout -b ${prBranchName}`);

const pakcageJson = JSON.parse(require('fs').readFileSync('package.json'));
const depStr = Object.keys(pakcageJson.dependencies || {}).join(' ');
const devDepStr = Object.keys(pakcageJson.devDependencies || {}).join(' ');
exec(`npm i ${depStr} ${devDepStr}`);

exec("git status");
exec("git add package*json");
exec(`git commit -m "chore(deps): update npm dependencies"`);
exec(`git push -fu origin ${prBranchName}`);

(async () => {
  await githubClient.pulls.create({
    base: baseBranchName,
    head: prBranchName,
    owner: context.repo.owner,
    repo: context.repo.repo,
    title: "chore(deps): update npm dependencies",
    body: "update npm dependencies",
  });
})();
```
<code>
</pre>

以下のように自動でプルリクを作ってくれます。
<img width="752" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/e114f208-1291-41a6-6ad2-fa816cbe5462.png">
上記のコードをちょっと変えればコミットメッセージやプルリクのタイトルを日本語にしたりできます。

この自動プルリクの可能性は、npmに限らずあらゆるパッケージマネージャや自動化ツールと連携してローカル環境に行かずに自動でプルリクエストできるところだと思います。
具体的には上記のコードの``exec(`npm i ${depStr} ${devDepStr}`)``の部分を他のコマンドに変えれば良いでしょう。例えばVue CLIを使っている人なら`vue upgrade`したやつをプルリクしてくれるのは便利かなと思います。


## プルリクをマージした時のプレビュー用のブランチを作る

comment-runの使用例です。
GitHub Actionsはセキュリティ上Secretsをforkした人が作ったプルリクには渡さないようにしています。（トークンとかがメンテナ以外が見れたりすると危険ですからね。）
それだとSecretsなしのGitHub Actionsしか実行できず機能的に制限されてしまいます。これが、それを解決するための一つの方法になります。
メンテナがプルリクエストのコードを見て問題ないときは以下の`@github-actions run ...`を実行するとマージした時のブランチを適当な名前でpushしてくれます。そのpushされたブランチへのURLが自動でコメントされます。
そのpushされたブランチではSecretsが引き渡された他のGitHub Actionsが動いてくれたりします。

<img width="762" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/e0165933-79ac-0292-9c9b-039e93a223df.png">
実際のコメント: <https://github.com/nwtgck/actions-comment-run/pull/2#issuecomment-596354791>
コードがほしいときは「実際のコメント」に飛んで取得できます。


## comment-runのセキュリティ的な話

コメントしたリポジトリにadmin/write権限のあるユーザーのみが実行できるようになっています。

それに加え、デフォルトでは`@github-actions run`でコメントを実行できるのは`OWNER`だけになっています。
例えば、最初の設定のYAMLで`allowed-associations: '["OWNER", "MEMBER"]'`に変更すればMEMBERも`@github-actions run`も実行できるようになります。
organizationに参加している人はMEMBERだったりします。

他には以下の値を取りうるはずです。

- COLLABORATOR
- CONTRIBUTOR
- FIRST_TIMER
- FIRST_TIME_CONTRIBUTOR
- MEMBER
- NONE
- OWNER

（参考: https://developer.github.com/v4/enum/commentauthorassociation/)


comment-runの環境は通常のGitHub Actionsの実行環境と同じだけの能力があります。つまりGitHub Actionsを書くときと同じように`@github-actions run`コメントを注意して書く必要があります。GitHub Actions用のGitHubのトークンが使えるのでそのリポジトリに対してのwrite権限があります。GitHub Actions用の秘密にすべき環境変数を設定している場合は表示しないように気をつけましょう。

## SSHしてGitHub Actions内に入る

comment-runの使用例に戻ります。
頑張ってSSHする方法は色々あると思います。ここでは @Cryolite氏提案の「[Piping Server を介した双方向パイプによる，任意のネットワークコネクションの確立 - Qiita](https://qiita.com/Cryolite/items/ed8fa237dd8eab54ef2f)」で紹介されていた方法を使います。

コメントを実行すると"New SSH session"というコメントが自動で投稿されそこに書かれているコマンドを別々のターミナルで実行するとSSHできます。
<img width="775" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/ffedc42a-d717-26fc-12dc-198e1cf475a3.png">
コードがほしいときは、実際のコメント: <https://github.com/nwtgck/actions-comment-run/pull/2#issuecomment-596020242> 

SSHで入った様子は以下のような感じです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/e23cde7f-c71e-18a7-eda6-3480c7a4f9cb.png" width="600">


SSHデバッグ専用のGitHub Actionsなら<https://github.com/mxschmitt/action-tmate>が有名だと思います。
上記のcomment-runも改良すればtmateを使った方法にできると思います。専用のActionsを使わずにcomment-runを一つ導入するだけで色々できるところが魅力です。



## comment-runは複数のコードブロック、任意の言語が実行できる

例えばGitHub Actionsの環境で`pip`が使えます。
以下のように`pip`でnumpyをインストールしてPythonが実行できます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/dd9ff94d-cb69-b612-e214-509ba3efd194.png" width="600">

重要なことは「**comment-runはJavaScriptとshebang（#!から始まるやつ）に対応している**」ということです。JavaScript以外には一つずつ言語に対応せずにshebangに対応するだけでシンプルさを保っています。shebangに対応するあらゆる言語などを実行できます。もう少し色んな言語を動かす例を見ていきます。

### Denoを動かす

以下がDenoを動かす例です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/eb148e40-ec93-3e5a-3356-2dd3c9e616df.png" width="600">
実際のコメント: https://github.com/nwtgck/actions-comment-run/pull/1#issuecomment-596170740
npmと違ってライブラリをURLでimportできるところがcomment-runとの相性が良さそうなところです。

### Goを動かす

以下でGo言語が動きます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/5455061b-3b07-13aa-4b13-708236ff540d.png" width="600">
実際のコメント: <https://github.com/nwtgck/actions-comment-run/pull/1#issuecomment-596176678>
残念ながら`go run`はshebangに対応しておらず`gorun`を使います。

### Haskellを動かす

以下でHaskellが動きます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/e26fc88f-021d-64f0-5941-f6ca9acd97e7.png" width="600">
実際のコメント: <https://github.com/nwtgck/actions-comment-run/pull/1#issuecomment-596176131>

### Scalaを動かす

以下でAmmoniteを使ってScalaを動かします。Denoと同じでbuild.sbtなどを使うのと違い他のライブラリを一つのファイルでimportしやすくcomment-runと相性が良いと思います。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/0c64855f-a5b0-40ae-980e-9bc8b68417f6.png" width="600">
実際のコメント: https://github.com/nwtgck/actions-comment-run/pull/1#issuecomment-596180626


### comment-runはコードブロックをシーケンシャルに実行する

comment-runの仕様の話です。
comment-runは```` ```js ````のコードブロックと shebang(#!のやつ) があるコードブロックを上から下へシーケンシャルに一つずつ実行します。

内部的には```` ```js ````はJavaScript Actionの環境で`eval()`します。そのためcomment-runのJavaScript Actionの環境にある変数が利用できます。（利用できることを保証している変数に関してはのちに追記したいと思います）
shebangが付いているものは一時ファイルとして保存して、実行権限をつけて実行します。


## ソースコードとしてGistやGitHubで管理したい

いろんな言語が動くというcomment-runの可能性を提示しましたが、JavaScriptを使うのが一番comment-runと相性がいいです。GitHubにコメントしたりtokenが使えるのはJavaScriptの環境だからです（環境変数やファイル経由で他の言語に引き渡す手はあると思います）。

そこで、
だんだんとロジックが複雑になるとソースコードとしてちゃんと管理したくなります。そこで簡単にGistで管理するTIPSです。

単純にGistに保存してそれを`fetch()`して`eval()`で実行するだけです。GitHubでもRawを押してコードのURLを使えばGitHubでもこの方法を使うことができます。
<img width="755" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/12e01feb-e7fe-9f43-1f73-911181e3050f.png">
Gist: <https://gist.githubusercontent.com/nwtgck/c8accac2bcd9939a6604a3500247012d/raw/comment-run-merge-preview.js>


## 既存のnpmパッケージ利用とTypeScript利用したいしちゃんとコード管理したい


だいぶ欲張りですが全部叶えたいです。

まず既存のnpmパッケージを利用したいです。例えば以下のように`npm i -S ...`好きにパッケージを入れられます。
<img width="571" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/350d4d06-1a4c-3300-e97f-fe819a55d8ac.png">

この方法は短い実行コメントの時や初期だと良いと思います。実行コメントが育ってきてだいぶ使ってきて動作が安定してきたら、ちゃんと依存関係を固定したり毎回`npm i `が走る時間を削減したくなってきます。
また、実行コメントが成長してくると、複雑なロジックやAPIなどの変化に強くするためにも型が欲しくなってきてTypeScriptで書きたくなってきます。
なおかつ、ソースコードを管理して他の人や再利用や改善がしやすい方法の紹介です。

仕組みとしてはTypeScriptで書いてnccでbundleしてできた.jsをGitHub Pagesにホストします。

利用する時は以下のようにGitHub Pagesからfetch()して使います。

<pre>
<code>
@github-actions run

```js
(async () => {
  const url = "https://nwtgck.github.io/comment-run-scripts/hello-world-comment.js";
  const js = await (await fetch(url)).text();
  eval(js);
})();
```
</code>
</pre>

[peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages)でビルド後の.jsをGitHub Pagesにホストすることで、通常のTypeScript Actionのようにビルド結果をGit管理する必要がなくなることも利点です。

ソースコードはここに<https://github.com/nwtgck/comment-run-scripts>あります。同じ用途でも個人でカスタマイズしたい場合やセキュリティ的な関係で自分の管理下のGitHub Pagesにしたい場合などは利用者はforkして運用するのがいいかなと思っています。

ディレクトリ構成は./hello-world-commentや./merge-previewなどがリポジトリ直下にあり、それらは独立したnpmパッケージなっています。各ディレクトリに入って好きに`npm i` することができます。

## comment-runで利用可能なことを保証しているJavaScriptの変数
以下のREADMEにまとめました。
<https://github.com/nwtgck/actions-comment-run/tree/6bea6fb49b8a3e2253f5e2e9ebfded12caf17113#available-variables-in-the-js-comment-context>