Github ActionsでDockerを使ってDB+HonoのAPIとReactのSPAを動かし、CucumberでE2Eテストを行いレポートをGithubPageに出力したメモ

# 概要
Azureのパイプラインで同様のことができたので、VSCode Copilot Agent(`Claude 3.7 Sonet`)を使ってGithub Action用に書き直してもらった。 ほとんど手直しせずに使えるものができた。

[この時点のソース](https://github.com/hibohiboo/odyssage/blob/49caf9238072ccaea050223ff399689d607bdce2/.github/workflows/github-pages.yml)

[デプロイしたBDDレポート](https://hibohiboo.github.io/odyssage/odyssage-e2e-output/cucumber-report.html)

# ソースコード
```yml:.github/workflows/github-pages.yml
name: Deploy Documents to Pages

on:
  push:
    branches:
      - develop
      - docs
    paths:
      - .github/workflows/**
      - docs/**
      - tests/**
      - src/**/tests/**
      - stories/**
  workflow_dispatch:
    
permissions:
  contents: read
  pages: write
  id-token: write
  
concurrency:
  group: 'pages'
  cancel-in-progress: false
  
jobs:
  # E2Eテストを実行
  e2e-tests:
    uses: ./.github/workflows/reusable-e2e-test.yml
  
  # ドキュメントを生成
  build-docs:
    needs: e2e-tests
    uses: ./.github/workflows/reusable-build-docs.yml
    with:
      build_path: ./docs/astro
  
  # GitHub Pagesにデプロイ
  deploy-pages:
    needs: build-docs
    uses: ./.github/workflows/reusable-deploy-pages.yml

```

Claudeは`reusable/e2e-test.yml`でいけると言っていたが、ChatGPTがサブディレクトリはだめだよ、といっていた。

[再利用可能なワークフローの作成](https://docs.github.com/ja/actions/sharing-automations/reusing-workflows#creating-a-reusable-workflow)

> 再利用可能なワークフローは、リポジトリの .github/workflows ディレクトリ内にあります。 workflows ディレクトリのサブディレクトリはサポートされていません。

これはChatGPTのほうが正しかった模様。

```yml:.github/workflows/reusable-e2e-test.yml
name: Reusable E2E Test Workflow

on:
  workflow_call:
    outputs:
      test_status:
        description: "E2Eテストの結果ステータス"
        value: ${{ jobs.bdd-test.outputs.status }}

jobs:
  bdd-test:
    name: BDD E2E Tests
    runs-on: ubuntu-latest
    outputs:
      status: ${{ steps.set-status.outputs.status }}
    
    # PostgreSQLサービスの設定
    services:
      postgres:
        image: postgres:17.4-alpine
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: main
        ports:
          - 5432:5432
        options: >-
          --health-cmd="pg_isready"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=5
          
    steps:
      # Node.js v22をセットアップ
      - name: Setup Node.js v22
        uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Checkout
        uses: actions/checkout@v4
        
      # Firebaseエミュレータのセットアップ
      - name: Install docker-compose
        run: |
          DOCKER_COMPOSE_PATH=/tmp/docker-compose
          sudo mkdir -p $DOCKER_COMPOSE_PATH
          sudo curl -SL https://github.com/docker/compose/releases/download/v2.35.0/docker-compose-linux-x86_64 -o $DOCKER_COMPOSE_PATH/docker-compose
          sudo chmod 755 $DOCKER_COMPOSE_PATH/docker-compose
          # バージョン確認でインストールを検証
          $DOCKER_COMPOSE_PATH/docker-compose version
          echo "$DOCKER_COMPOSE_PATH" >> $GITHUB_PATH
        
      - name: Start Firebase Emulator
        run: |
          cd infra/local/firebase
          
          # ディレクトリの内容を確認（デバッグ用）
          ls -la
          
          # docker-composeファイルの存在を確認
          if [ ! -f "docker-compose.ci.yml" ]; then
            echo "::error::docker-compose.ci.yml が見つかりません"
            exit 1
          fi
          
          # docker-composeを使用してFirebaseエミュレータを起動
          docker-compose -f docker-compose.ci.yml up -d firebase
          
          # エミュレータの起動を確認するための待機ロジック
          echo "Waiting for Firebase Auth emulator..."
          MAX_RETRIES=20
          RETRY_COUNT=0
          RETRY_INTERVAL=2
          
          while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
            RETRY_COUNT=$((RETRY_COUNT + 1))
            echo "Attempt $RETRY_COUNT/$MAX_RETRIES"
            
            # Firebase Auth エミュレータの状態確認
            if curl -s http://localhost:9099/ > /dev/null; then
              echo "Firebase Auth emulator is ready"
              break
            fi
            
            # デバッグ情報の追加
            docker ps
            docker logs $(docker ps -q --filter "name=firebase") 2>/dev/null || echo "Firebase container logs not available"
            
            # 最大試行回数に達した場合はエラー
            if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
              echo "::error::Firebase Auth emulator failed to start within timeout period"
              exit 1
            fi
            
            sleep $RETRY_INTERVAL
          done
          
      # Bunのセットアップ
      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
          
      - name: Install Dependencies
        run: bun install
        
      # Playwrightのセットアップ
      - name: Install Playwright
        run: npx playwright install --with-deps chromium
        working-directory: apps/frontend
        
      # 環境変数の設定
      - name: Setup Backend Environment
        run: cp .dev.vars.ci .dev.vars
        working-directory: apps/backend
        
      - name: Setup Frontend Environment
        run: cp .env.ci .dev.local
        working-directory: apps/frontend
        
      # ホスト名の設定
      - name: Setup hosts file
        run: |
          echo "127.0.0.1 db.localtest.me" | sudo tee -a /etc/hosts
        
      # PostgreSQLの起動確認
      - name: Wait for PostgreSQL
        run: |
          echo "Waiting for PostgreSQL..."
          for i in {1..20}; do
            if PGPASSWORD=postgres psql -h db.localtest.me -U postgres -d main -c "SELECT 1" &>/dev/null; then
              echo "PostgreSQL is ready"
              break
            fi
            if [ $i -eq 20 ]; then
              echo "::error::PostgreSQL failed to start within timeout period"
              exit 1
            fi
            echo "Attempt $i/20 - PostgreSQL not ready yet, waiting..."
            sleep 2
          done
          # 接続情報を表示（デバッグ用）
          PGPASSWORD=postgres psql -h db.localtest.me -U postgres -d main -c "\conninfo"
        
      # Neon Proxyの起動
      - name: Start Neon Proxy
        run: |
          echo "Starting Neon Proxy..."
          # Docker内からホストのPostgreSQLに接続するための設定
          docker run -d --name neon-proxy \
            -e PG_CONNECTION_STRING=postgres://postgres:postgres@host.docker.internal:5432/main \
            -p 4444:4444 \
            --add-host=host.docker.internal:host-gateway \
            ghcr.io/timowilhelm/local-neon-http-proxy:main
          
          # プロキシが起動したことを確認
          sleep 5
          docker ps | grep neon-proxy || { echo "::error::Neon Proxy failed to start"; exit 1; }
          
          # ログを表示（デバッグ用）
          docker logs neon-proxy
        
      # バックエンドとフロントエンドの起動
      - name: Start Backend
        run: |
          bun run dev > backend.log 2>&1 &
          echo $! > ../backend.pid
        working-directory: apps/backend
        
      - name: Start Frontend
        run: |
          bun run preview:ci > frontend.log 2>&1 &
          echo $! > ../frontend.pid
        working-directory: apps/frontend
        
      # サーバー起動待機
      - name: Wait for servers
        run: |
          echo "Waiting for frontend server..."
          for i in {1..30}; do
            if curl -s http://localhost:5173 > /dev/null; then
              echo "Frontend server is up."
              break
            fi
            if [ $i -eq 30 ]; then
              echo "::error::Frontend server failed to start within timeout period"
              echo "Frontend server logs:"
              cat apps/frontend/frontend.log
              exit 1
            fi
            echo "Attempt $i/30 - Frontend server not ready yet, waiting..."
            sleep 2
          done

          echo "Waiting for backend server..."
          for i in {1..30}; do
            if curl -s http://localhost:8787 > /dev/null; then
              echo "Backend server is up."
              break
            fi
            if [ $i -eq 30 ]; then
              echo "::error::Backend server failed to start within timeout period"
              echo "Backend server logs:"
              cat apps/backend/backend.log
              exit 1
            fi
            echo "Attempt $i/30 - Backend server not ready yet, waiting..."
            sleep 2
          done
          
        
      # データベースマイグレーション
      - name: Run database migration
        run: bun run migrate:ci
        working-directory: packages/database
        
      # BDD E2Eテストの実行
      - name: Run BDD E2E Tests
        id: run_tests
        run: bun run bdd-test-debug
        working-directory: packages/bdd-e2e-test
        env:
          CI: true
        continue-on-error: true
        
        
      # リソースのクリーンアップ
      - name: Cleanup resources
        if: always()
        run: |
          kill $(cat apps/backend/backend.pid) || true
          kill $(cat apps/frontend/frontend.pid) || true
          docker stop neon-proxy || true
          docker rm neon-proxy || true
          

      # E2Eテスト出力を次のジョブで使用するためにキャッシュに保存
      - name: Cache E2E test output for documentation
        if: always()
        uses: actions/cache@v4
        with:
          path: packages/bdd-e2e-test/output
          key: e2e-output-${{ github.run_id }}
```

テスト結果をキャッシュ経由で、GithubPagesへデプロイするフォルダにコピーする。

```yml:.github/workflows/reusable-build-docs.yml
name: Reusable Document Build Workflow

on:
  workflow_call:
    inputs:
      build_path:
        description: 'ドキュメントビルドパス'
        required: true
        type: string

jobs:
  build:
    name: Build Documents
    runs-on: ubuntu-latest
    # dbdoc生成のためにPostgreSQLのコンテナを立ち上げる
    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: main
        ports:
          - 5432:5432
          
    steps:
      # Node.js v22をセットアップ
      - name: Setup Node.js v22
        uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Checkout
        uses: actions/checkout@v4
        with:
          # 後述のcode-maatでgit logを取得するために必要
          fetch-depth: 0
          
      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
          
      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v5

      - name: Install document dependencies
        run: bun install
        working-directory: ${{ inputs.build_path }}

      # ドキュメント作成用の依存関係をインストール
      - name: Install monorepo dependencies
        run: bun install --ignore-scripts

      # マイグレーションを実行
      - name: Migration
        run: bun run migrate:ci
        working-directory: ./packages/database

      # dbdoc作成
      - name: Build dbdoc with Schemaspy
        run: docker compose up
        working-directory: ./docs/dbdoc

      # OpenAPI ドキュメント作成
      - name: Build with OpenAPI
        run: bun install && bun run build
        working-directory: ./docs/redocly

      # build storybook
      - name: Build Storybook
        run: bun install && bun run build-storybook
        working-directory: ./packages/ui

      # ドキュメント作成
      - name: Build with Astro
        run: |
          npm run astro build \
            --site "${{ steps.pages.outputs.origin }}" \
            --base "${{ steps.pages.outputs.base_path }}"
        working-directory: ${{ inputs.build_path }}

      # eslint ドキュメント作成
      - name: Build with eslint-inspect
        run: bun run build-inspect
        working-directory: ./packages/eslint-config-custom
      
      # hotspot ドキュメント作成
      - name: Set up JDK 21
        uses: actions/setup-java@v4.5.0
        with:
          distribution: 'temurin'
          java-version: '21'

      - name: Download Code Maat
        run: wget https://github.com/adamtornhill/code-maat/releases/download/v1.0.4/code-maat-1.0.4-standalone.jar
        working-directory: ./docs/code-maat

      - name: create git log 
        run: git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN' --no-renames --after=2024-01-01 > ./logfile.log
        working-directory: ./docs/code-maat

      - name: Run Code Maat
        run: java -jar code-maat-1.0.4-standalone.jar -l logfile.log -c git2 -a revisions > ./revisions.csv
        working-directory: ./docs/code-maat

      - name: Run Cloc
        run: docker run --rm -v .:/tmp aldanial/cloc --unix --by-file --csv --quiet --timeout 20 --vcs=git --exclude-dir=docs,.vscode,.github --not-match-f=\.json --report-file=./docs/code-maat/complexity.csv

      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
          
      - run: python csv_as_enclosure_json.py --structure complexity.csv --weights revisions.csv > ../astro/dist/crime-scene-hotspots/hotspots.json
        working-directory: ./docs/code-maat
        
      # E2Eテスト出力を前のジョブからキャッシュで取得
      - name: Restore E2E test output
        uses: actions/cache@v4
        with:
          path: packages/bdd-e2e-test/output
          key: e2e-output-${{ github.run_id }}

      # E2Eテスト結果をAstroのビルドディレクトリにコピーする
      - name: Copy E2E test results to Astro build directory
        if: always()
        run: |
          mkdir -p ${{ inputs.build_path }}/dist/odyssage-e2e-output
          if [ -d "packages/bdd-e2e-test/output" ]; then
            cp -r packages/bdd-e2e-test/output/* ${{ inputs.build_path }}/dist/odyssage-e2e-output/
            echo "E2Eテスト結果を ${{ inputs.build_path }}/dist/odyssage-e2e-output/ にコピーしました"
          else
            echo "E2Eテスト出力ディレクトリが見つかりません。ディレクトリを作成します。"
            mkdir -p ${{ inputs.build_path }}/dist/e2e-output
            echo "<html><head><title>E2E Test Results</title></head><body><h1>E2Eテスト結果</h1><p>テスト結果はありません</p></body></html>" > ${{ inputs.build_path }}/dist/e2e-output/index.html
          fi

      # astro ドキュメントアップロード
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ${{ inputs.build_path }}/dist

```

```yml:.github/workflows/reusable-deploy-pages
name: Reusable Deploy to Pages Workflow

on:
  workflow_call:

jobs:
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

```


# 参考
[AzureDevOpsのパイプライン上でFirebaseEmulatorをdocker-composeを使って起動したメモ](https://qiita.com/hibohiboo/items/933d1dc7b12f47d68113)
[Azure DevOps の Pipeline で動かす cucumberからエラーログをとったメモ](https://qiita.com/hibohiboo/items/6ec2b418ea41c672d96a)
[Azure DevOps の Pipeline のDocker でPostgres+Neon Proxyのローカルサーバを動かしたメモ](https://qiita.com/hibohiboo/items/0eaa79057e41dd30c147)

[GitHubPagesに開発ドキュメントをデプロイする総集編](https://qiita.com/hibohiboo/items/efff5fe687076ab44561)