Supabase の Edge Function を github で管理する覚書

Next.js + Supabase の運用で Edge Function をリポジトリ管理に入れて github からデプロイする覚書です。
この記事を参考にした結果何かしらの不具合が起きても責任は負わないものとします。

Edge Function については別の記事を参考にして下さい。
「Supabase の Edge Function で Hello World する覚書」
https://qiita.com/jackalope/items/452ce72555eeed66a6f0

## 試した環境
- Windows11
- Next.js v15.1.6

## 準備
Supabase から アクセストークン と プロジェクトID を取得しておきます。
アクセストークンは Supabase Account settings の **Generate new token** から作成出来ます。
https://supabase.com/dashboard/account/tokens

取得出来たら Github の Settings に進み、**Secrets and variables** からシークレットを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4026777/a4497c6c-9674-4603-ae8f-74b5604cc4d7.png)

**PROJECT_ID** と **SUPABASE_ACCESS_TOKEN** という２つの変数を設定しておきます。
これは Github Actions から参照する変数になります。
設定出来たらこんな感じになってると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4026777/e04399e9-6c0a-44dc-9240-be99373f376d.png)


## リポジトリ
ここからはローカルの作業です。
前提としてルートに **supabase** フォルダがあるものとします。
Edge Function はそのフォルダ内に配置します。

## tsconfig.json
まず **tsconfig.json** に **supabase** フォルダを無視させる設定をします。
これにより vercel での lint エラーも防止出来ます。
```
 "exclude": ["supabase"]
```
既に **node_modules** など書かれていると思うので
```
"exclude": ["node_modules", "supabase"]
```
のようにカンマで追記する形になります。

## GHA 設定
Github Actions を設定します。
最初に **.github/workflow** フォルダを作成します。
このフォルダにアクションを入れておくと github が自動で実行してくれるようになります。

アクションは reddit を参考にさせて頂きました。
https://www.reddit.com/r/Supabase/comments/13if16p/automatically_deploy_supabase_edge_functions_with/
```deploy-edge-functions.yml
# .github/workflows/deploy-edge-functions.yml
name: Deploy Function       # GitHub Actions の表示名

on:
  push:                     # main ブランチに push されたとき
    branches: [main]
    paths:                  # かつ supabase/functions/ 以下に変更があるとき
      - supabase/functions/**
  workflow_dispatch:        # Actions タブから手動実行も可

jobs:
  deploy:
    runs-on: ubuntu-latest  # GitHub の Ubuntu VM で実行

    # ここで CLI が参照する環境変数を渡す
    env:
      SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      PROJECT_ID: ${{ secrets.PROJECT_ID }}

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Install Supabase CLI
        uses: supabase/setup-cli@v1
        with:
          version: latest   # 任意。latest でも OK

      #- name: Initialize Supabase CLI
      #  run: |
      #    if [ ! -d "supabase" ]; then
      #      supabase init
      #    fi
      - name: Link Supabase project
        run: supabase link --project-ref "$PROJECT_ID"

      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v35
        with:
          files: supabase/functions/**      # supabase/functions 配下だけを対象

      - name: Store changed files
        id: impacted
        run: |
          # 変更されたファイル＋_shared を再帰解析して
          # 実際に更新の必要がある Edge Function ディレクトリ名だけを列挙
          echo -n "CHANGED_FILES=${{steps.changed-files.outputs.all_changed_files}}" >> $GITHUB_ENV

      # Get a list of all the edge functions that may be impacted by the changes.
      # This includes functions in the functions directory that have changed and
      # any files in the _shared directory that are imported by the edge functions
      # in the functions directory.
      - name: Get impacted functions
        id: impacted_functions
        run: |
          impactedFunctions=()
          checkedFiles=()
          filesToCheck=($CHANGED_FILES)
          while [ ${#filesToCheck[@]} -gt 0 ]; do
            # Get the next file to check.
            file=${filesToCheck[0]}
            unset filesToCheck[0]
            filesToCheck=(${filesToCheck[*]})

            # Skip files that have already been checked.
            if [[ "${checkedFiles[*]}" == *"$file"* ]]; then
              continue
            fi

            # Mark the current file as checked.
            checkedFiles+=("$file")

            # Get the folder and file names for the current file.
            folder=$(dirname "$file")
            folder_name=$(basename "$folder")
            file_name=$(basename "$file")

            # If the current file is in the `_shared` directory,
            # check for any files that import from it.
            if [ "$folder_name" == "_shared" ]; then
              # Search for files that import from the current file.
              if output=$(grep -r -l "import .* from .*/${file_name}" . --only-matching 2>/dev/null); then
                # Add any matching files to the list of files to check.
                while read -r line; do
                  filesToCheck+=("$line")
                  matched_files+=("$line")
                done <<< "$output"
              fi
            # If the current file is not in the _shared directory, we assume that it's a function file that
            # needs to be deployed. We add the parent folder (i.e. the function name) to the impactedFunctions array.
            else
              impactedFunctions+=("$folder_name")
            fi
          done

          # Convert the array to a string and store it in the environment variable
          impactedFunctions=$(echo "${impactedFunctions[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')
          echo -n "IMPACTED_FUNCTIONS=$impactedFunctions" >> $GITHUB_ENV

      - name: Deploy Functions
        run: |
          for fn in $IMPACTED_FUNCTIONS; do
            supabase functions deploy "$fn" --project-ref "$PROJECT_ID"
          done
```
ここでは **deploy-edge-functions.yml** というファイルにしました。
このスクリプトにより、**supabase/functions** フォルダ以下の deno コードに変更があった場合のみ Supabase へのデプロイが実行されるという運用が出来てます。

## デプロイ
後は通常通りプッシュします。
Github Actions の実行状況は **Actions** タブから確認出来ます。
通知設定を変えてなければメール通知も来ると思います。