CLOSED

Zenn の Scraps 的なものを GitHub Issues を CMS として実現する

  • 要件

    • 逐次、タイムライン的な形で更新できる
    • Markdown で入力できる
    • クローズできる
    • タグをつけられる
    • ブログ本体のデプロイ毎に GitHub API を使いすぎないようにする
      • Issue の更新トリガーで JSON を生成するような感じがよさそ
    • 並び順は作成の降順?
    • 各コメントにアンカーつけたい
  • yml
    # yshrsmz/scraps
    name: dispatch scrap update
    
    on:
      issues:
      issue_comment:
    
    jobs:
      create-scrap:
        # yshrsmz.github.io の workflow を参照
        uses: yshrsmz/yshrsmz.github.io/.github/workflows/_create-scrap.yml@scraps
        with:
          issue_number: ${{ github.event.issue.number }}
        secrets:
          target_github_token: ${{ secrets.GITHUB_TOKEN }}
    yml
    # _create-scrap.yml
    jobs:
      create-scrap:
        runs-on: ubuntu-latest
        steps:
          - name: create scrap
            # action も workflow も同じレポジトリ内ではあるが、ここもフルパスで指定する必要有り
            uses: yshrsmz/yshrsmz.github.io/packages/action-create-scrap@scraps
            with:
              issue_number: ${{ github.event.inputs.issue_number }}
              github_token: ${{ secrets.target_github_token }}
  • repository_dispatch するか、 yshrsmz.github.io を clone & push するか

    repository_dispatch だと各レポジトリで PAT が必要になるから、 clone & push のほうが楽ぽい

  • ページネーション、これでいけるらしい

    ts
    const comments = await octokit.paginate(
        octokit.rest.issues.listComments,
        {
          ...repo,
          issue_number: issueNumber,
          per_page: 10,
        },
        (response) => response.data,
      )
  • commit は git config が必要 credential は @actions/coretoken を与えると persist してくれる

    • paths loader は本文を content として渡すことができるが、html か Markdown を想定しているようなので、今回の用途には合わない
    • data loader を別で作る必要あり
      • GitHub REST Api は issue や comment を plain text で返すので、 data loader で MarkdownIt をかませる必要あり
    ts
    import { createMarkdownRenderer, type SiteConfig } from 'vitepress'
    
    export default {
      watch: ['data/scraps/*.json'],
      async load(watchedFiles: string[]) {
        // config は VITEPRESS_CONFIG
        const config: SiteConfig = (global as any).VITEPRESS_CONFIG
    
        const md = await createMarkdownRenderer(
          // ...
        )
    
        return watchedFiles
          .map((file) => readFileSync(file, 'utf-8'))
          .map<Scrap>((json) => JSON.parse(json))
          .map((scrap) => {
            return {
              ...scrap,
              body: md.render(scrap.body),
              comments: ... // comments の body も md.render() する
            }
          })
      }
    }
  • shell
    echo '${{ needs.create-scrap.outputs.output }}' > ./packages/blog/data/scraps/${{ github.event.issue.number }}.json

    だと、JSON に含まれる single quote が消えてしまう 🤔

  • 一度環境変数に入れてあげればいけそう

    yml
    - run: |
        echo $SCRAP_JSON > ./packages/blog/data/scraps/${{ github.event.issue.number }}.json
      env:
        SCRAP_JSON: ${{ needs.create-scrap.outputs.output }}
  • 変数パターンだと連続したスペースがまとめられちゃってそう。インデントが崩れる。

    double quote で囲ってあげるといける

    diff
      - run: |
    -     echo $SCRAP_JSON > ./packages/blog/data/scraps/${{ github.event.issue.number }}.json
    +     echo "$SCRAP_JSON" > ./packages/blog/data/scraps/${{ github.event.issue.number }}.json
        env:
          SCRAP_JSON: ${{ needs.create-scrap.outputs.output }}
  • 残作業

    • 対象 issue の限定方法
      • 指定の label がついた issue を exclude するか include するか
    • ↑の方法で後日更新されて対象に含まれなくなった issue をどうやって削除するか
    • Scrap/各コメントの作成日, 更新日時の表示
  • 除外設定のラベルをつけるのがよさそ そっちなら余計なラベルをフィルターする処理も不要

このスクラップはに完了しました