型別生成與 CI/CD:讓 GitHub Actions 自動守住型別與部署 | Supabase 完整教學

2026/10/31
型別生成與 CI/CD:讓 GitHub Actions 自動守住型別與部署 | Supabase 完整教學

這是 Supabase CLI 段的收尾。前面幾篇我們把 schema 變更變成帶時間戳的遷移檔、打通了 db push 的單向部署,又用 Branching 讓每個 PR 都有隔離的預覽環境。本篇要做的,是把「型別生成、遷移部署、函式上線」這些重複、機械、該自動跑的事,全部交給 GitHub Actions:用 supabase gen types typescript 自動生成 TypeScript 型別、再以 git diff --exit-code 在 CI 裡守住型別與 schema 的同步;用 supabase/setup-cli 搭配 SUPABASE_ACCESS_TOKEN,自動跑 db lint、db push 部署遷移、functions deploy 上線 Edge Functions,並實作 staging → production 的兩段式部署。走完這篇,你的整條後端工作流就會跑成一台不用人顧的機器。

前言

先一句話定義本篇要打造的東西:這是一條「型別自動同步 + 遷移自動部署」的 CI/CD(持續整合/持續部署)流水線——每次有人開 PR、每次 merge 進主要分支,GitHub Actions 都會自動幫你檢查型別有沒有跟上 schema、把遷移套到正確的環境、把 Edge Functions 部署上線,全程不需要人手動去 db push 或記得「要生成型別」。

用一個類比理解它的價值。如果說遷移檔是「你想對資料庫做的每一筆變更的書面提案」,那 CI/CD 就是「自動化的審查與施工團隊」。 過去這些事是人工的:改完 schema,你得記得重新生成型別、記得 commit、記得連到正確的專案、記得 db push、記得部署函式——任何一步漏掉,就可能是「前端拿著過期型別」或「忘了推遷移導致線上壞掉」。CI/CD 把這整套變成:提案(PR)一送出,機器就自動檢查、自動施工,而且每一步都留下 git 紀錄可稽核。

而這一切能自動化的地基,正是前幾篇打下的:遷移檔(schema 的真相來源)、gen types(從 schema 生型別)、db push(部署遷移)、functions deploy(部署函式)——本篇不引入新指令,而是把這些既有指令編排進 GitHub Actions 的 workflow,讓它們在對的時機自動跑起來。

本篇你會學到:

  • 型別自動同步的守門機制:用 gen types + git diff --exit-code 在 CI 裡強制「schema 一改、型別必須跟著更新並提交」
  • GitHub Actions 部署遷移與函式:用 supabase/setup-cli + SUPABASE_ACCESS_TOKEN,自動跑 db lint、db push、functions deploy
  • staging → production 兩段式部署:develop 分支自動部署 staging、main 分支自動部署 production,並用 --dry-run 與審核關卡把關
  • 與 Branching 搭配:Branching 管開發期預覽、CI/CD 管合併後推進,兩者如何接成一條完整流水線

本篇聚焦「型別生成與 CI/CD」。CLI 安裝、本機環境、遷移工作流、Branching 已在前幾篇講透,這裡假設你已經能在本機 supabase start、用 migration new/db diff 產生遷移、也理解 Branching 的預覽環境。CI/CD 的實際 action 版本、CLI 旗標、計費與權限細節都可能隨版本調整,正式導入前一律以官方文件為準。

核心概念

在寫 workflow 之前,先建立兩張心智圖:一是「型別如何自動同步、CI 如何守門」,二是「遷移與函式如何依分支自動部署到不同環境」。

一、型別自動同步:生成不是重點,守門才是

supabase gen types typescript 能從 PostgreSQL 的 schema 自動生成 TypeScript 型別定義(database.types.ts),讓前端拿到完整的型別安全與自動補全。但真正棘手的問題不是「怎麼生成」,而是「怎麼確保這份型別檔永遠和 schema 同步」——只要有人改了 schema 卻忘了重新生成並 commit,前端就會拿著過期的型別寫程式,直到執行期才炸掉。

CI 的解法很聰明:在乾淨環境重新生成一次型別,然後用 git diff 比對它和 repo 裡那份是否一致。 流程長這樣:

①  supabase db reset          在乾淨環境套用所有遷移,得到最新 schema
②  supabase gen types ... --local > database.types.ts
                              從這個 schema 生成型別,覆蓋 repo 裡的那份
③  git diff --exit-code database.types.ts
                              比對:有差異就以非 0 退出、CI 失敗

關鍵在第 ③ 步的 --exit-code:

  • 若沒有差異(git diff 為空、exit code = 0):代表你 commit 的型別檔和「從最新 schema 生成的版本」一致 → CI 通過。
  • 若有差異(exit code ≠ 0):代表有人改了 schema 卻沒更新型別檔 → CI 失敗,擋在 merge 之前。

換句話說,這一步的價值是「守門」不是「生成」:它把「忘了更新型別」這種人為疏漏,變成機器會自動抓出來、擋下來的失敗。

二、遷移與函式:依分支自動部署到不同環境

第二張心智圖,是「哪個分支的變更,自動部署到哪套環境」。一個典型的兩段式部署策略如下:

Git 分支                     GitHub Actions 自動做的事               目標環境
─────────────────────────────────────────────────────────────────────
PR(對 main/develop)  ──►   驗證:db reset + gen types + diff + lint   (只驗證,不部署)
│
develop 分支(push)   ──►   link staging → db push → functions deploy   Staging
│
main 分支(push)      ──►   link prod → db push --dry-run → db push
                            → functions deploy(+ 手動審核關卡)          Production

三層各司其職:

  • PR 階段只驗證、不部署:確保遷移能乾淨重播、型別有同步、db lint 沒問題,把關進 merge 的品質。
  • develop → Staging:merge 進 develop 就自動部署到長期存在的 staging 專案,做整合測試。
  • main → Production:merge 進 main 才部署到正式環境,通常加 --dry-run 先看清楚將執行的 SQL、並用 GitHub Environment 的手動審核關卡把關。

三、關鍵術語與 secret 速查

CI 要能代表你操作雲端,需要幾個關鍵的 secret 與 action。先建立對照:

名稱角色
supabase/setup-cli@v1官方 GitHub Action,在 runner 上安裝 Supabase CLI
SUPABASE_ACCESS_TOKEN個人存取令牌,CI 的身分憑證(取代本機的 supabase login)
PROJECT_ID(project-ref)專案 Reference ID,指定 link 要連到哪一個專案
SUPABASE_DB_PASSWORD資料庫密碼,db push 套用遷移時需要
git diff --exit-code有差異就以非 0 退出,用來讓「型別沒同步」變成 CI 失敗

一句話記法:access token 是「我是誰」、project-ref 是「連哪個專案」、DB password 是「進那個資料庫」——三者分工,全部存進 GitHub Secrets,絕不寫死在 workflow 裡。

實作範例

下面一步步把三份 workflow 建起來:① PR 驗證(型別守門 + lint) → ② 部署到 Staging → ③ 部署到 Production。所有檔案放在 .github/workflows/ 底下。

一、準備 GitHub Secrets

在動手寫 workflow 前,先到 GitHub 儲存庫的 Settings → Secrets and variables → Actions 新增這幾個 secret(名稱可自訂,但要和 workflow 對得上):

SUPABASE_ACCESS_TOKEN     個人存取令牌(Dashboard → Account → Access Tokens 產生)
STAGING_PROJECT_ID        staging 專案的 Reference ID
STAGING_DB_PASSWORD       staging 資料庫密碼
PRODUCTION_PROJECT_ID     production 專案的 Reference ID
PRODUCTION_DB_PASSWORD    production 資料庫密碼

SUPABASE_ACCESS_TOKEN 是所有雲端操作的身分憑證,一組通常可共用;PROJECT_ID 與 DB_PASSWORD 則依 staging/production 各存一份,分開管理能避免誤推。

二、把型別生成整合進本機工作流(package.json)

在寫 CI 之前,先讓本機也能一鍵生成型別,這樣「schema 改完就順手重生型別再 commit」才會變成習慣。在 package.json 加入 scripts:

{
  "scripts": {
    "types:local": "supabase gen types typescript --local > database.types.ts",
    "types:remote": "supabase gen types typescript --linked > database.types.ts"
  }
}

日常流程就變成:改遷移 → supabase db reset 驗證 → npm run types:local 重生型別 → git add 兩者一起 commit。CI 要守的,正是「你有沒有乖乖做最後這步」。

三、Workflow ①:PR 驗證(型別守門 + lint)

第一份 workflow 在每個 PR 上跑,不碰任何雲端專案——它只在 runner 本機跑一套 Supabase,驗證遷移、型別、lint 三件事:

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches: [main, develop]
  workflow_dispatch:

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Supabase CLI
        uses: supabase/setup-cli@v1
        with:
          version: latest

      # 只啟動資料庫,比完整 supabase start 更輕量,適合 CI
      - name: Start local database
        run: supabase db start

      # 在乾淨環境重播所有遷移,驗證遷移能從零套起來
      - name: Verify migrations
        run: supabase db reset

      # 從最新 schema 重新生成型別,覆蓋 repo 裡那份
      - name: Generate TypeScript types
        run: supabase gen types typescript --local > database.types.ts

      # 守門:若生成結果和 commit 進來的不同,git diff 以非 0 退出、CI 失敗
      - name: Check types are in sync
        run: |
          if ! git diff --exit-code database.types.ts; then
            echo "::error::型別已過期。請在本機執行 npm run types:local 並提交變更。"
            exit 1
          fi

      # schema 語法與安全性檢查
      - name: Lint database
        run: supabase db lint

      - name: Stop local stack
        run: supabase stop
        if: always()

逐段講解:

  • supabase/setup-cli@v1:官方 action,負責在 runner 上裝好 CLI,version: latest 取最新版(正式環境可釘特定版本以求穩定)。
  • supabase db start + db reset:在 runner 本機起一套資料庫、把所有遷移重播一遍——這一步本身就在驗證「遷移能從零乾淨套起來」。
  • gen types + git diff --exit-code:本篇的主角。生成型別後比對差異,有差就失敗。注意 if ! git diff --exit-code ...; then ... exit 1——git diff --exit-code 在有差異時回傳非 0,我們據此印出明確錯誤訊息並讓 job 失敗。
  • db lint:順手做 schema 的語法與安全性檢查(如隱式型別轉換、SQL 注入風險等)。
  • supabase stop with if: always():無論前面成功或失敗都收拾環境。

四、Workflow ②:部署到 Staging(develop 分支)

第二份 workflow 在 push 進 develop 時觸發,把遷移與函式部署到 staging 專案:

# .github/workflows/deploy-staging.yml
name: Deploy to Staging

on:
  push:
    branches: [develop]
  workflow_dispatch:

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging
    env:
      SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      SUPABASE_DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}
    steps:
      - uses: actions/checkout@v4

      - uses: supabase/setup-cli@v1
        with:
          version: latest

      # 用 project-ref 連結到 staging 專案
      - name: Link to staging project
        run: supabase link --project-ref ${{ secrets.STAGING_PROJECT_ID }}

      # 部署前先看一眼本機與遠端的遷移狀態對照
      - name: Check migration status
        run: supabase migration list

      # 把尚未套用的遷移推到 staging
      - name: Push migrations
        run: supabase db push

      # 部署所有 Edge Functions 到 staging
      - name: Deploy Edge Functions
        run: supabase functions deploy

逐段講解:

  • env: 區塊放認證:把 SUPABASE_ACCESS_TOKEN 與 SUPABASE_DB_PASSWORD 設在 job 層級的 env,底下每個 step 的 CLI 指令都讀得到——這是 CI 非互動認證的關鍵,CLI 靠環境變數裡的 token 代表你操作雲端。
  • supabase link --project-ref ...:用 staging 的 project-ref 把這次操作指向 staging 專案。
  • supabase db push:把本機 supabase/migrations/ 裡尚未套用的遷移,套到 staging 資料庫。
  • supabase functions deploy(不接名稱):部署 supabase/functions/ 底下所有函式;若只想部署單一函式,可寫 supabase functions deploy <name>。
  • environment: staging:對應 GitHub 的 Environment 設定,可在此掛上保護規則(雖然 staging 通常不需人工審核)。

五、Workflow ③:部署到 Production(main 分支)

第三份 workflow 在 push 進 main 時觸發,部署到正式環境——和 staging 幾乎一樣,但多了兩道保險:--dry-run 先看 SQL、以及靠 environment: production 掛上手動審核關卡:

# .github/workflows/deploy-production.yml
name: Deploy to Production

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment: production   # 在 GitHub 設定為需人工審核的 Environment
    env:
      SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      SUPABASE_DB_PASSWORD: ${{ secrets.PRODUCTION_DB_PASSWORD }}
    steps:
      - uses: actions/checkout@v4

      - uses: supabase/setup-cli@v1
        with:
          version: latest

      - name: Link to production project
        run: supabase link --project-ref ${{ secrets.PRODUCTION_PROJECT_ID }}

      # 乾跑:只顯示將執行哪些 SQL,不實際套用——上生產前的最後一眼
      - name: Dry-run migration push
        run: supabase db push --dry-run

      # 確認無誤後,實際套用遷移到 production
      - name: Push migrations
        run: supabase db push

      - name: Deploy Edge Functions
        run: supabase functions deploy

和 staging 版的差異,正是「上生產該有的謹慎」:

  • environment: production:在 GitHub 的 Environment 設定裡,可為 production 加上「required reviewers(必要審核者)」,讓這個 job 在真正執行前卡住、等人按核准——這是防止破壞性變更直接自動衝上正式環境的重要關卡。
  • supabase db push --dry-run:先乾跑一次,把「將要執行的 SQL」印出來讓你(或 log)確認,不會實際改動資料庫;確認無誤,下一步才真正 db push。

六、進階:排程自動更新型別(選用)

除了「PR 守門」,你也可以讓 CI 定期從 production 生成最新型別、若有變化就自動開一個 PR。這對「schema 偶爾被 Dashboard 直接改動」的團隊很有用:

# .github/workflows/update-types.yml
name: Update Database Types

on:
  schedule:
    - cron: '0 2 * * *'   # 每天 UTC 02:00
  workflow_dispatch:

jobs:
  update-types:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: supabase/setup-cli@v1
        with:
          version: latest

      # 直接用 project-id 生成,不必先 link
      - name: Generate types from production
        run: |
          supabase gen types typescript \
            --project-id ${{ secrets.PRODUCTION_PROJECT_ID }} \
            > database.types.ts
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

      # 型別有變才自動開 PR
      - name: Create PR if types changed
        uses: peter-evans/create-pull-request@v6
        with:
          commit-message: "chore: update database type definitions"
          title: "chore: update database type definitions"
          branch: "chore/update-database-types"
          delete-branch: true

這裡用到 supabase gen types typescript --project-id <id>——不必先 link,直接指定專案生成,適合這種一次性任務。

常見錯誤與最佳實踐

坑一:改了 schema 卻忘了重新生成型別、直接 commit。 這是最常見、也最隱蔽的坑:程式碼跑得起來、本機也沒報錯,直到某天前端用到新欄位、卻發現型別裡根本沒有,或用了已被刪掉的欄位、執行期才炸。正確做法:把 Workflow ① 的「gen types + git diff --exit-code」守門機制建起來——讓「型別沒同步」變成一個會失敗的 CI 檢查,而不是靠人自律。本機也搭配 npm run types:local,養成「改遷移、重生型別、一起 commit」的肌肉記憶。

坑二:access token 權限過大,或直接寫死在 workflow 裡。 把 SUPABASE_ACCESS_TOKEN 硬編碼在 yaml、或印到 log 裡,等於把雲端專案的鑰匙公開。正確做法:所有 token、project-ref、DB password 一律存進 GitHub Secrets,用 ${{ secrets.XXX }} 引用;token 以最小必要權限產生;定期輪替;若不慎外洩,立刻到 Dashboard 撤銷該 token 並重新產生。secret 絕不進 git、絕不進 log。

坑三:沒有 staging、直接把變更推 production。 圖快省事、跳過 staging,讓 main 一 merge 就衝上正式環境,一旦遷移有問題就是線上事故。正確做法:落實 staging → production 兩段式——develop 先部署 staging 做整合驗證,確認無虞才 merge 進 main 上 production;production 部署前一律 --dry-run 看清楚 SQL,並用 GitHub Environment 的必要審核者關卡,讓上生產這一步「一定有人按過核准」。

坑四:CI 只部署、不驗證,或遷移根本沒在乾淨環境測過。 只寫了 deploy workflow、卻沒有 PR 驗證,等於遷移沒經過「能不能從零乾淨重播」的考驗就上線。正確做法:Workflow ① 的 db reset(乾淨重播所有遷移)是最基本的品質關卡,務必納入;再加上 db lint 抓 schema 的語法與安全問題。先驗證、後部署,別讓沒測過的遷移直接進正式環境。

坑五:破壞性遷移沒演練,靠 CI 一鍵自動衝上生產。 CI/CD 讓部署變得太順手,反而放大了破壞性變更(drop column、改型別)的風險——merge 一下就自動生效在 production。正確做法:破壞性變更前,先在 Branching 的 preview 環境或 persistent staging 完整演練;遷移拆成「向後相容的多步」(先加新欄位、程式雙寫過渡、確認後才在後續遷移刪舊);production 部署前務必 --dry-run 確認 SQL,搭配審核關卡人工把關。

最佳實踐總結:

  • 型別靠 CI 守門:gen types + git diff --exit-code,讓「沒同步」自動變成 CI 失敗
  • secret 全進 GitHub Secrets:access token、project-ref、DB password 絕不寫死、絕不進 log
  • 兩段式部署:develop → staging 驗證、main → production 上線,別跳過 staging
  • 先驗證、後部署:PR 一律跑 db reset + db lint,遷移先確認能乾淨重播
  • 上生產要謹慎:--dry-run 看 SQL + GitHub Environment 審核關卡,破壞性變更先演練
  • 細節以官方為準:action 版本、CLI 旗標、權限與計費都可能隨版本調整

小結

這是 Supabase 系列教學的第 042 篇,也是整個 CLI 段(SB-5)的收尾。上一篇《Branching 分支開發》讓每個 PR 都有一套隔離的預覽環境,解決了「開發階段」的協作隔離;這一篇則把「合併之後」的自動化補上——用 GitHub Actions 串起「型別守門 → 遷移部署 → 函式上線」的完整 CI/CD 流水線。

核心觀念濃縮成一句話:用 supabase gen types typescript + git diff --exit-code 在 CI 裡強制型別與 schema 同步;用 supabase/setup-cli 搭配 SUPABASE_ACCESS_TOKEN、project-ref、DB password 三個 secret,讓 GitHub Actions 自動跑 db lint、db push 部署遷移、functions deploy 上線函式;再以「develop → staging、main → production」的兩段式部署(加上 --dry-run 與審核關卡),把上一篇 Branching 的開發期預覽,接成一條從本機到生產、每一段都自動化又可審核的完整後端流水線。

  • 型別守門:CI 生成型別後用 git diff --exit-code 比對,沒同步就失敗,擋在 merge 前
  • CI 認證:SUPABASE_ACCESS_TOKEN(身分)+ project-ref(哪個專案)+ DB password(進資料庫),全存 GitHub Secrets
  • 自動部署:supabase/setup-cli → db lint/db push/functions deploy,依分支部署到對應環境
  • 兩段式與搭配:develop → staging、main → production,配 --dry-run 與審核;Branching 管開發預覽、CI/CD 管合併推進

到這裡,Supabase CLI 與本機開發、遷移、Branching、CI/CD 這一整段就完整了:你已經能在本機用 Docker 跑起完整堆疊、用遷移管理每一次 schema 變更、給每個 PR 一套隔離預覽,並讓整條部署流水線自動化運轉。下一篇《自架部署(Docker Compose)》,我們會離開 Supabase 雲端,走進另一條路線——用 Docker Compose 把整套 Supabase 服務自己架在自己的伺服器上,聊聊自架的架構、設定與取捨。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。

本週主打

AI 自動化入門包

你每天手動在做的那些煩事,其實 AI 可以自己跑。這份給你 10 個照著做就會的自動化工作流 + 50 個複製即用的提示詞,不用會寫程式。

看看這個產品 →