簡単なGitHubActionsの実装くらい脳死で生成AI叩いてたらええやろ思うじゃん

## はじめに
GitHubActionsはビジネスロジックもあまり入ってこないし、大きなリポジトリだったり複雑なサービス構成でもない限りやることも似通っている。
それなら、生成AIにやらせるのがぴったりだと思い「:point_up:簡単なGitHubActionsは生成AIに作らせよう」というこぎみの良いタイトルで記事を書きながら検証を始めましたが、確かに助けにはなりますが、思ったよりすんなりいかない生成AIの現実、当然のようにハルシネーションにでくわしたのでその過程と回避策や注意点をベタにまとめようと思います。

また、生成AI、ChatGPTどう使っている、使うときはどのあたりを気をつけている、と聞かれた時のとてもベタな作業レベルでイメージが共有できたらなという思いで記述しています。

## 前提として

- doc以下のmdファイルの更新を検知するとhtmlに変換してpublicディレクトリにpushするworkflowの実装を目指す
- 利用する生成AIはChatGPTのモデル「ChatGPT 4o」

```bash:project
📂 .github/
  └── 📂 workflows/
      └── 📄 doctohtml.yaml # 成果物
📂 doc/
  └── 📄 test.md
📂 public/
```

## 本記事で表現したいこと

- 生成AIを使った試行錯誤の過程とうまく利用するための注意点
- GithubActionsによる md -> htmlの自動変換のコード
  - [実装結果①-pandoc](#実行結果-2-完成)
  - [実装結果②-markdown-text-to-html](#実行結果-3上記の引数追加で成功)

---

### 課題:doc以下のmdファイルの更新を検知するとhtmlに変換してpublicディレクトリにpushするworkflowを実装する

#### 命令①：箇条書きで実現したいロジックを記述
プロンプトを真剣に学んでる方からはなんとかしろと言われかねない内容ですが、内容も内容ですしこんなもんでいけます。

```
以下の条件でGitHubActionsを組んでくれ

・リポジトリに含まれているmdファイルをHTMLに変換する
・その際、docディレクトリ配下にあるmdファイルの変更を検知して実行する。
・pushされると、実行されて変換したHTMLがPUSHされる。
```

#### GPT回答①：ロジックの流れはこの時点で完成
上記の質問のみでロジックの流れはこの段階で概ね完成します(以下展開式のパネル)。処理を追ってみると問題ないように見えます。pandocというライブラリを使っていることをチェックします。

<details><summary>GPT-4oの1回目の回答 - エラーあり</summary>

```yaml:GPT回答①
name: Convert Markdown to HTML

on:
  push:
    branches:
      - main  # 実行したいブランチを指定
    paths:
      - 'doc/**/*.md'  # docディレクトリ配下の.mdファイルのみ検知

jobs:
  convert-md-to-html:
    runs-on: ubuntu-latest
    steps:
      # リポジトリのチェックアウト
      - uses: actions/checkout@v4

      # 変更されたMarkdownファイルのリストを取得
      - name: Get changed markdown files
        id: changed-files
        uses: tj-actions/changed-files@v45
        with:
          files: |
            **.md

      # Pandocをインストール
      - name: Install Pandoc
        run: sudo apt-get install -y pandoc

      # MarkdownファイルをHTMLに変換
      - name: Convert Markdown to HTML
        if: steps.changed-files.outputs.any_changed == 'true'
        env:
          ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
        run: |
          for file in ${ALL_CHANGED_FILES}; do
            if [[ $file == doc/*.md ]]; then
              base_name=$(basename "$file" .md)
              pandoc -f markdown -t html "$file" -o "public/${base_name}.html"
            fi
          done

      # 変換したHTMLをGitHubにコミット
      - name: Commit HTML files
        if: steps.changed-files.outputs.any_changed == 'true'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions@users.noreply.github.com"
          git add public/*.html
          git commit -m "Auto-convert Markdown to HTML by GitHub Actions"
          git push

```

</details>

:::note info
この時点でロジックの流れ自体はもうほぼ完成しています。それほど難しいことをしているわけではありませんが、大事なファイルの存在チェックの判定や、忘れがちなshellの文法など、秒で作成してくれるのは頼もしい限りです。
:::


#### 実行結果①：PUSHでエラー

![スクリーンショット 2024-09-22 22.23.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197171/695ed074-1eed-a313-5552-c0f9c974093a.png)

git push大丈夫かなと思ったらやっぱり出ます。すかさず再度GPT4oに質問します。
出てきたGithubActionsのエラーをそのまま貼り付けます。

#### 命令②:雑にエラーを貼って原因を聞く
これどうしよ、はいらないです。

```
これどうしよ

Run git config user.name "github-actions[bot]"
  git config user.name "github-actions[bot]"
  git config user.email "github-actions@users.noreply.github.com"
  git add public/*.html
  git commit -m "Auto-convert Markdown to HTML by GitHub Actions"
  git push
  shell: /usr/bin/bash -e {0}
[main 95bbb97] Auto-convert Markdown to HTML by GitHub Actions
 1 file changed, 55 insertions(+)
 create mode 100644 public/test.html
remote: Permission to xxxxx/githubactions-sanbox.git denied to github-actions[bot].
fatal: unable to access 'https://github.com/xxxxx/githubactions-sanbox/': The requested URL returned error: 403
Error: Process completed with exit code 128.
```

#### GPT回答②:エラーの解決策を記述、設定周りの確認も合わせて説明。
ソースは省略して貼りますが、趣旨はGITHUB_TOKENをつっこんでくれ、とのことでgit push時にGITHUB_TOKENを使うように以下のように修正します。

```yaml:GPT回答②
  # 変換したHTMLをGitHubにコミット
    （...省略）
      git commit -m "Auto-convert Markdown to HTML by GitHub Actions"
      # git push


  - name: Push changes to GitHub
    if: steps.changed-files.outputs.any_changed == 'true'
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    run: |
      git push https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git
```

また、リポジトリ設定の確認も要求されました。説明されたリポジトリの設定欄は微妙に現状の状態と違いましたが、同等の箇所はわかるので、これを持ってGitHubから設定を修正します。以下がGPTがくれた回答です。

> デフォルトの GITHUB_TOKEN がリポジトリにプッシュできないように設定されている可能性があります。この場合、リポジトリの設定から GITHUB_TOKEN の権限を確認する必要があります。
> 1. リポジトリの Settings -> Actions -> General に移動します。
> 2. Workflow permissions の設定を確認してください。
>    Read and write permissions を選択して、GitHub Actionsがリポジトリに書き込みできるようにします。

![スクリーンショット 2024-09-22 22.05.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197171/715d28ec-9a3a-d730-5a9a-ad39c9ef44aa.png)

Actionsのyaml内でできることだろうと疑いググった結果。permissionsを見つけますが、一旦よしとします。(※このあと、これはpermissionsでできるだろうと尋ねると、GPTはコードを提示してくれました)

```
permissions:
  contents: write
```

#### 実行結果②: markdown->html自動生成が動作するようになった、が、生成されたHTMLにスタイルがない。

コードに問題ありませんでした、が、今回pandocで出力されたHTMLの内容はスタイル適用がなく、いまいちです。これでは使えません。

![スクリーンショット 2024-09-22 22.32.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197171/270253fa-b47e-8bae-0fb1-3db5b986ae3e.png)


#### 命令③-1：pandocによるスタイル適用を依頼する
ここで、pandocで生成するHTMLにスタイル適用してくれ、と聞きました。が、最初適当な１行分のスタイルのcssを示したり、手動で作り出すので、ここで自分はpandocにはいわゆるテンプレのようなものは組み込まれておらず、自分で選んでくるものなのか、と早合点し、別のライブラリによる作成に舵を切ります。それが命令④以降です。

```
質問例①：
phandocで適用して

質問例②：
phandocでもスタイル指定すればmarkdown綺麗に出力できるんだね。
githubactionsならどうかく？
```

#### GPT回答③-1：CSSファイルの作成とスタイリング適用方法を提案
見る気も失せるcssカスタムを提案してくれました。いやこういうものはテンプレートだろうとpandocが知らなくても想像はつくのでスキップ。

```yaml:GPT回答③-1
# おすすめカスタムCSSを作成
- name: Create custom CSS
run: |
  echo "body { font-family: Arial, sans-serif; line-height: 1.6; margin: 20px; }" > custom.css
  echo "h1, h2, h3 { color: #333; }" >> custom.css
  echo "code { background-color: #f4f4f4; padding: 2px 4px; border-radius: 4px; }" >> custom.css
  echo "table { border-collapse: collapse; width: 100%; }" >> custom.css
  echo "th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }" >> custom.css
  echo "th { background-color: #f2f2f2; }" >> custom.css

（省略）

  pandoc "$file" -o "./public/${base_name}.html" --css=custom.css
```

:::note alert
命令③-1 -> 命令③-2の間にGoogle検索で情報収集の時間があります。いい感じにスタイル入れてよ、ではGPTはpandocのtemplateを利用するやり方を一向に提案してくれませんでした。③-1での察してくれなさは個人的には意外でした。
:::

#### 命令③-2：Google検索で[pandocのtemplates](https://github.com/ryangrose/easy-pandoc-templates)の存在を把握し、その上で質問を投げる
命令③-1でもらった結果は実行せず、pandocについて調べてテンプレートの適用方法について把握し、具体的なコマンドを投げつつ、改めてGPTに命令します。

```
このテンプレートの命令をロジックに入れて挙げて
pandoc -s --toc --template=easy-pandoc-templates/html/elegant_bootstrap_menu.html sample.md -o sample.html

あと、テンプレートのこれインストールも忘れずに
curl 'https://raw.githubusercontent.com/ryangrose/easy-pandoc-templates/master/copy_templates.sh' | bash
```

#### GPT回答③-2：pandocのテンプレートを指定したHTML出力部分をズバリ回答

やっと察してくれました。
また、sample.mdなど適当な指定していても、これを置き換えて今までのロジックの中にディレクトリ内のループで出力してくれています。

```yaml:GPT回答③-2
- name: Download Pandoc templates
run: |
  curl 'https://raw.githubusercontent.com/ryangrose/easy-pandoc-templates/master/copy_templates.sh' | bash

# MarkdownファイルをHTMLに変換
- name: Convert Markdown to HTML with Template
if: steps.changed-files.outputs.any_changed == 'true'
env:
  ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
  for file in ${ALL_CHANGED_FILES}; do
    base_name=$(basename "$file" .md)
    pandoc -f markdown -t html --template=easy-pandoc-templates/html/elegant_bootstrap_menu.html "$file" -o "public/${base_name}.html"
  done
```

#### 実行結果③-2: 完成

完成しました。

![スクリーンショット 2024-09-23 20.41.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197171/31265dd6-40d3-abe0-6840-da9103bc909a.png)

```yaml:md->htmlの自動変換(pandoc利用)
name: Convert Markdown to HTML fix

on:
  push:
    branches:
      - phandoc-ver
      - main  # 実行したいブランチを指定
    paths:
      - 'doc/**/*.md'  # docディレクトリ配下の.mdファイルのみ検知

jobs:
  convert-md-to-html:
    runs-on: ubuntu-latest
    steps:
      # リポジトリのチェックアウト
      - uses: actions/checkout@v4

      # 変更されたMarkdownファイルのリストを取得
      - name: Get changed markdown files
        id: changed-files
        uses: tj-actions/changed-files@v45
        with:
          files: |
            **.md

      # Pandocをインストール
      - name: Install Pandoc
        run: sudo apt-get install -y pandoc

      - name: Download Pandoc templates
        run: |
          curl 'https://raw.githubusercontent.com/ryangrose/easy-pandoc-templates/master/copy_templates.sh' | bash

      # MarkdownファイルをHTMLに変換
      - name: Convert Markdown to HTML
        if: steps.changed-files.outputs.any_changed == 'true'
        env:
          ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
        run: |
          for file in ${ALL_CHANGED_FILES}; do
            if [[ $file == doc/*.md ]]; then
              base_name=$(basename "$file" .md)
              pandoc -f markdown -t html -s --template=easy_template --metadata title="${base_name}" "$file" -o "public/${base_name}.html"
            fi
          done

      # 変換したHTMLをGitHubにコミット
      - name: Commit HTML files
        if: steps.changed-files.outputs.any_changed == 'true'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions@users.noreply.github.com"
          git add public/*.html
          git commit -m "Auto-convert Markdown to HTML by GitHub Actions"
      
      - name: Push changes to GitHub
        if: steps.changed-files.outputs.any_changed == 'true'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git push https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git

```


___


そして、もう一つ、この命令③からGPT回答③-2の回答で完成としたmd→html出力ですが、**命令③-1のGPTの回答で右往左往している間に**、pandoc以外の別のライブラリを見つけて、試したりもしました。

#### 命令④-1：pandocの代わりにと、URLを共有しつつ見つけたライブラリの書き換えを提案。

```
https://github.com/marketplace/actions/converts-markdown-text-to-html
こいつを使って、markdown -> htmlするようにしたのを、
(省略：上述のソース貼り付け)
にあてはめて再度出力してほしい
```

#### GPT回答④-1: 存在しないアクションをひいてしまう

```yaml: GPT回答④-1
(省略)
- name: Convert Markdown to HTML
if: steps.changed-files.outputs.any_changed == 'true'
uses: tj-actions/markdown-to-html@v2
with:
  source: '${{ steps.changed-files.outputs.all_changed_files }}'
  destination: 'public/'
(省略)
```

良さそうに見える回答が返ってきました、が、vscodeはそんなものはないと赤線を引くありさま。よく見ると渡したURLとは全然違うActionを指定していることに気がつきます。

![スクリーンショット 2024-09-22 22.47.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197171/1c48ecbc-8fe3-adb2-dfe5-e5b804bcf301.png)

古いものなのか、はたまた存在してなかったのか、どちらにせよ、URLでページを読ませてやってくれというのは厳しいようです。この回答を受けて命令を変えます。

#### 命令④-2: アクションではなく、手動コマンドでの実行を提案。
GitHub MarketPlaceが怪しいのなら、npmパッケージの方は情報も古くからあったのかもしれないと、そちらで書き換えを依頼してみます。

```　
※意訳：何回かに分けて質問してます
nodejs入れてmarkdown-to-html-cliを手動でインストールして、今のpandocでやってることやって
```

#### GPT回答④-2: 書いてくれる
良さそうな内容で出力してくれました。
```yaml: GPT回答④-2
(省略)
# Node.jsとnpmをインストール
- name: Install Node.js
  run: |
    sudo apt update
    sudo apt install -y nodejs npm

# markdown-to-html-cliをインストール
- name: Install markdown-to-html-cli
  run: |
    npm install -g markdown-to-html-cli

# MarkdownファイルをHTMLに変換
- name: Convert Markdown to HTML
  if: steps.changed-files.outputs.any_changed == 'true'
  run: |
    for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
      base_name=$(basename "$file" .md)
      mkdir -p ./public
      markdown-to-html "$file" -o "./public/${base_name}.html"
    done
(省略)
```

#### 実行結果④-2: 存在しないパスを指定しエラー
Convert Markdown to HTMLブロックでエラーになってしまいました。

```
(省略)
Run for file in doc/test.md; do
  for file in doc/test.md; do
    base_name=$(basename "$file" .md)
    mkdir -p ./public
    markdown-to-html "$file" -o "./public/${base_name}.html"
  done
  shell: /usr/bin/bash -e {0}
  
node:fs:1391
  handleErrorFromBinding(ctx);
  ^
Error: ENOTDIR: not a directory, mkdir '/home/runner/work/githubactions-sanbox/githubactions-sanbox/public/test.html/doc'
    at Object.mkdirSync (node:fs:1391:3)
    at module.exports.makeDirSync (/usr/local/lib/node_modules/markdown-to-html-cli/node_modules/fs-extra/lib/mkdirs/make-dir.js:23:13)
    at file:///usr/local/lib/node_modules/markdown-to-html-cli/lib/index.js:81:16
(省略)
```
mkdirでおこっているように見えたので、mkdir -p ./public を消しましたが、当然変わりません。-pはディレクトリが存在しない場合のみ作るの意です。
#### 命令④-3: 上記のエラーの内容をべたに質問

どうやらmarkdown-to-htmlのコマンドが怪しそうですが、一旦ベタに聞いてしまいます。

```
エラーだとさ

(省略)
[実行結果④-2:貼り付け]
(省略)
```

#### GPT回答④-3:回答は変わらない。

出力先が間違っているからエラーになる、という回答ですが、答えとして提示されるコードが変わりません。これはかなりGPTあるあるです。こうなった時点でGPTとはお別れするべきです。markdown-to-htmlのコマンドの調整を期待しましたがそうはなりませんでした。

改めて[公式サイト](https://github.com/marketplace/actions/converts-markdown-text-to-html)を見に行くと、シンプルに引数を間違えていた。というより足りてませんでした。

```
🙅‍♀️間違い
markdown-to-html "$file" -o "./public/${base_name}.html"
↓
🙆正しい
markdown-to-html --source "$file" -o "./public/${base_name}.html"
```

:::note alert
これが修正内容です。とGPTが言って返ってきた回答が前と変わらない場合は、GPTを見限る。こちらからもGPTがわかっていないことがはっきりと自覚できる分、嘘情報よりは安易か
:::

#### 実行結果④-3:上記の引数追加で成功。

上記の引数指定により無事に出力されました。
こちらのライブラリは、デフォルトでDarkモードLightモードの切り替えのJSイベントがついてくるなど魅力的でしたが、nodejsをインストールする手間などでpandoc側のアクションの実行速度が速かったこともあり③の方を採用しました。

```yaml:markdown-to-html-cli編
name: Convert Markdown to HTML fix

on:
  push:
    branches:
      - main
      - phandoc-ver  # 実行したいブランチを指定
    paths:
      - 'doc/**/*.md'  # docディレクトリ配下の.mdファイルのみ検知

jobs:
  convert-md-to-html:
    runs-on: ubuntu-latest
    steps:
      # リポジトリのチェックアウト
      - uses: actions/checkout@v4

      # Node.jsとnpmをインストール
      - name: Install Node.js
        run: |
          sudo apt update
          sudo apt install -y nodejs npm
         
      # markdown-to-html-cliをインストール
      - name: Install markdown-to-html-cli
        run: |
          npm install -g markdown-to-html-cli

      # 変更されたMarkdownファイルのリストを取得
      - name: Get changed markdown files
        id: changed-files
        uses: tj-actions/changed-files@v45
        with:
          files: |
            **.md

      # MarkdownファイルをHTMLに変換
      - name: Convert Markdown to HTML
        if: steps.changed-files.outputs.any_changed == 'true'
        run: |
          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
            base_name=$(basename "$file" .md)
            echo "base_name: $base_name"
            echo "file: $file"
            markdown-to-html --source "$file" -o "./public/${base_name}.html"
          done

      # 変換したHTMLをGitHubにコミット
      - name: Commit HTML files
        if: steps.changed-files.outputs.any_changed == 'true'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions@users.noreply.github.com"
          git add public/*.html
          git add doc/*.html
          git commit -m "Auto-convert Markdown to HTML by GitHub Actions"
      
      - name: Push changes to GitHub
        if: steps.changed-files.outputs.any_changed == 'true'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git push https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git

```


### 結論

ChatGPTは知らないことを知らないと言ってくれないのが出現当時から今も変わらぬハマりやすい盲点かと思います。要はハルシネーションです。

- 最初に投げて返ってくる一筆書きのようなロジックは「おおむね」頼りになる
- URLを共有して「〇〇を踏まえた上で解いてくれ」の聞き方はあまり信用できない(2024年9月のGPT4o時点)
- ググって調べて、生成されたコードの技術の最新情報、公式ドキュメントをきちんとあたること。
  - GitHubActionsの[最新情報](https://github.blog/changelog/label/actions/https://github.blog/changelog/label/actions/)をチェック
  - [pandocの公式](https://pandoc-doc-ja.readthedocs.io/ja/latest/users-guide.html#specifying-formats)
- GPTから同じ回答が返ってきた時は諦める
- ビジネスロジックや秘匿情報は突っ込まないように、ということを考えると、GitHubActionsあたりはやっぱり利用しやすい


### 余談

<details><summary>「:point_up:簡単なGitHubActionsは生成AIに作らせよう」のタイトルで記事を書き出した時の「はじめに」</summary>
CI/CDの考え方自体には歴史があり、Jenkins,Travis CI,CircleCI,AzurePipelines,GitHubActionsなどツールも数々生まれて来ました。

歴史ありツールは様々ありますが、基本的にやることは変わっていません。ビルド、テスト(検証含む)、デプロイ、それぞれの自動化を目論むものであることは変わっていません。

上記の歴史も踏まえ、以下の理由からGitHubActionsは生成AI(ChatGPT-4o)に任せてしまうのが何よりと考えています。

- CI/CDでやることは基本的には決まっている。ビルド、テスト、デプロイ、という目的はプロジェクトがいくら複雑化しても変わることはありません
- ビジネスロジックが入る余地はない。プロジェクトごとに必要な固有のカスタマイズがあるのは当然だと思いますが、生成AIに食わせるに躊躇うようなロジックが入ることは少ないです

</details>