【Hugo】GitHubをヘッドレスCMSとして使う

:::note
2024年2月23日 追記
GitHub Actions内のコマンドにミスがあったので修正しました。

```yaml:変更前
run: github-issue-cms --token=${{ secrets.GH_TOKEN }}
```
```yaml:変更後
run: github-issue-cms generate --token=${{ secrets.GH_TOKEN }}
```

:::

こんにちは。

今回はGitHubをHugoのCMSとして活用してみました。

今回作成したツールは以下のリポジトリで公開しています。

https://github.com/rokuosan/github-issue-cms

## Hugo + Headless CMS

私のブログではHugoをSSGとして利用しています。

新しく記事を執筆するときは、執筆環境にサイト管理用のリポジトリやVSCode、Hugoを用意してから執筆することになります。

この作業は一度やれば済むので、別にそこまで辛くはないのですが、問題は画像を配置するときです。

VSCodeを使って執筆している時に、新規に画像を追加すると以下の手順が発生します。
```mermaid
flowchart TD
    A(スクリーンショットを撮る) --> B(画像を保存)
    B --> C(imagesディレクトリに配置)
    C --> D(ショートコードを書く);
```

これを画像を追加するたびに行うのは非常に面倒です。

そこで、**ヘッドレスCMS**の登場です。これを利用すれば、QiitaやZennのようにクリップボードにある画像をそのまま配置できたり、画像をWordPressのように管理できたりします。

Hugoと相性の良いヘッドレスCMSを検索してみるとForestryやNetlify CMSなどがヒットします。
しかしながら、ForestryはTina CMSというプロジェクト変わっていたり、Netflify CMSはデプロイ先が限られたりとやや面倒そうです。

実際にTina CMSについては使用してみましたが、  UIが好みではなく、設定についてもやや限られている印象でした。（私が使いこなせてない可能性も大いにありますが...）

ここで、私は欲しいヘッドレスCMSは以下のような特徴を持つものです。

- DBMSを必要としない
- いつでも執筆環境にアクセスできる
- ノーコストで運用ができる

## GitHubという選択肢

みなさんご存知の通り、GitHubのIssueには優秀なMarkdownエディタがあります。
このエディタはクリップボードにある画像も貼り付けることができます。
テンプレート機能を利用すれば、以下のように記事の雛形を登録しておくことも可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447932/cab7a519-9e42-534f-6446-28d20555786e.png)

そして、GitHubはAPIを提供しており、GitHub上のデータを自由に利用できます。
ということで、CMSに必要なフロント部分としてGitHubを活用し、記事の生成部分を自分で作ります。


## 作ったもの

https://github.com/rokuosan/github-issue-cms

今回作成したものは、GitHub上のIssueを記事として扱います。
実行時にCloseされているIssueの本文を記事のコンテンツとしてファイルに保持します。

タグやカテゴリについては、それぞれラベルとマイルストーンが対応します。

Hugoが提供するフロントマター機能については、Issueの先頭にコードブロックを記述することで任意のコンテンツを挿入できるようにしています。

また、画像についても、IssueをMarkdownとして保存する際にダウンロードとリンクの張り替えを行います。

## 既存のHugoプロジェクトに組み込む

今回作成したものは、CIに組み込むことを想定しています。この記事の場合、データをすべてGitHub上に保管しており、CIとしてGitHub Actionsを利用しました。

GitHub Actionsで利用する際に、Issueへのアクセス権を持ったトークンを発行しておきます。

``https://github.com/settings/tokens``から新規Classicトークンを発行します。この時に**repo**スコープに権限を与えるようにしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447932/98af4cc9-79cc-1459-896f-ae1210630af4.png)

発行したトークンをブログを管理しているリポジトリのシークレットに``GH_TOKEN``という名前で登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447932/016d6f31-c98d-cd8a-7d00-258d717106b0.png)

次にリポジトリのルートディレクトリに``gic.config.yaml``というYAMLファイルを作成します。

``github.username``に自分のユーザ名、``github.repository``に対象のリポジトリ名を記します。

``hugo.url.images``には、Hugoで画像を配置するディレクトリ名を書きます。

```yaml: gic.config.yaml
github:
  username: 'your-name'
  repository: 'your-repository'

hugo:
  url:
    images: '/your-images-directory'
```

最後に以下のようなワークフローを定義します。

```yaml
name: Go

on:
  push:
    branches: [ "main" ]
  issues:
    types: [reopened, closed]
  pull_request:
    branches: [ "main" ]
  workflow_dispatch:

permissions: write-all

jobs:

  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.21.4'

    - name: Install Tools
      run: go install github.com/rokuosan/github-issue-cms@latest

    - name: Generate
      run: github-issue-cms generate --token=${{ secrets.GH_TOKEN }}

    - name: Auto Commit
      uses: stefanzweifel/git-auto-commit-action@v4
      with:
        commit_message: "ci: :memo: Update articles"

    - name: Clouflare Pages Webhook
      run: |
        curl -X POST ${{ secrets.CF_WEBHOOK_URL }}
```

私はデプロイにCloudflare Pagesを利用しているため、ジョブの最後にCloudflareのデプロイ用Webhookに対してPOSTするようにしています。


## Issue を書く

ワークフローを登録しているリポジトリで、Issueを書きます。

このとき、カスタムフロントマターを利用しやすくするために、Issueのテンプレートを作成しておくことをお勧めします。

リポジトリに``.github/ISSUE_TEMPLATE``という名前でディレクトリを作成し、以下のようなMarkdownを作成します。

~~~Markdown: article.md
---
name: Article
about: Template for a new article
title: ''
labels: ''
assignees: ''
---

```
slug: 'your-article-slug'
# url: '/articles/your-article-url'
```

## Title

~~~

## デプロイの確認

最後に正常にデプロイがされているか確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447932/a69faea9-85d8-d52d-b15c-bd198503c058.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447932/b2db1c45-4f64-c0dd-125f-7fc6a51c64d1.png)

GitHubとCloudflareでビルドにかかる時間はそれぞれ30秒程度なので、Issueをクローズしてから1分ほどでデプロイが完了します。

作成したツールのインストールに時間がかかっているみたいなので、ビルド済みのものをリリースすれば時間短縮ができそうですね...。

## 終わりに

GitHubで記事が書けるとなると、今までの心理的ハードルもさがり、筆も走るような気がします。

また、外部サービスを利用しないので料金がかからないのでお財布にも優しいですね。その代わりGitHub APIの仕様変更などがあれば対応しなければなりませんが...。