﻿# 如何使用 Chrome Web Store Publish API 自動化發佈 Chrome Extension

我現在維護著幾套不同的 Chrome 擴充套件，每次發佈新版本時都需要手動上傳更新，覺得有點麻煩，所以我這幾天研究了一下 [Chrome Web Store Publish API](https://developer.chrome.com/docs/webstore/using-api) 並設定了 [GitHub Actions](https://github.com/features/actions) 來自動化發佈 Release 與 Chrome Extension，真的方便太多了。這篇文章我就來說說這個設定的過程。

![An abstract futuristic visualization of software automation and deployment.](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/793182278548d56ad2f6-afdca185-0396-429a-b5e1-c2c035911c07.webp)

### 完整的 GitHub Actions 設定檔

以下是我完整的 GitHub Actions 設定檔 (`publish.yml`)，該檔案必須放在 Repo 的 `.github/workflows/publish.yml` 路徑，這樣當你的 `main` 分支有新的 commit 或手動觸發 workflow 時，GitHub 就會執行這個設定檔。

```yaml
name: 發行版本與發布至 Chrome Web Store

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  publish:
    runs-on: windows-latest

    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4

      - name: 設定 Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: 取得版本號
        id: get_version
        run: |
          $version = (Get-Content manifest.json | ConvertFrom-Json).version
          echo "version=$version" >> $env:GITHUB_OUTPUT

      - name: 建立 GitHub Release
        id: create_release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: v${{ steps.get_version.outputs.version }}
          name: Release v${{ steps.get_version.outputs.version }}
          draft: false
          prerelease: false

      - name: 建立 ZIP 封裝檔
        run: |
          $filePath = "FeloSearchToolkitExtension_v${{ steps.get_version.outputs.version }}.zip"
          7z a $filePath _locales images src CHANGELOG.md manifest.json README.*

      - name: 上傳 Release 附件
        uses: softprops/action-gh-release@v1
        with:
          tag_name: v${{ steps.get_version.outputs.version }}
          files: ./FeloSearchToolkitExtension_v${{ steps.get_version.outputs.version }}.zip

      - name: 發布至 Chrome Web Store
        uses: mnao305/chrome-extension-upload@v4.0.1
        with:
          file-path: 'FeloSearchToolkitExtension_v${{ steps.get_version.outputs.version }}.zip'
          extension-id: ${{ secrets.EXTENSION_ID }}
          client-id: ${{ secrets.CLIENT_ID }}
          client-secret: ${{ secrets.CLIENT_SECRET }}
          refresh-token: ${{ secrets.REFRESH_TOKEN }}
```

> 備註: 我主要使用 [mnao305/chrome-extension-upload](https://github.com/mnao305/chrome-extension-upload/) 工具來發佈 Chrome 擴充套件。

### 設定 Chrome Web Store Publish API

[Chrome Web Store Publish API](https://developer.chrome.com/docs/webstore/api) 可以讓你透過 REST API 建立、更新和發佈物件到 Chrome Web Store，但是第一次設定稍微有點繁瑣。

你的主要工作有：

1.  先 [手動發佈擴充套件](https://developer.chrome.com/docs/webstore/publish)，並取得 `Extension ID`
    
2.  到 [Google Cloud Console](https://console.cloud.google.com/) 選擇一個專案 (GCP Project)
    
3.  到 [Google Cloud Console > APIs & Services](https://console.cloud.google.com/apis/dashboard) 啟用 `Chrome Web Store API`
    
4.  到 [Google Cloud Console > APIs & Services > Credentials](https://console.cloud.google.com/apis/credentials) 建立 **OAuth 2.0 用戶端 ID** 和**用戶端密鑰** (`Client ID` & `Client Secret`)
    
5.  到 [Google Cloud Console > Google Auth Platform > Audience](https://console.cloud.google.com/auth/audience) 切換 **Publishing status** 為 `In production`
    
    Google Cloud 的 Project 在建立後，預設的**發佈狀態**為 `Testing` (測試中)，這個狀態在透過 Token Endpoint 取得 Access Token 與 Refresh Token 時，你的 Refresh Token 會在 7 天後過期，這樣會導致你的 CI / CD 作業超過七天就無法自動化發佈了。
    
    因此，你必須將**發佈狀態**切換到 `In production` 才可以取得沒有期限的 `Refresh Token` (刷新金鑰)！🔥
    
6.  用 [OAuth 2.0 Playground](https://developers.google.com/oauthplayground/) 取得 `Refresh Token` (刷新金鑰)
    
    點擊右上角的設定圖示，然後勾選 `Use your own OAuth credentials`
    
    輸入你的 `OAuth Client ID` 與 `OAuth Client secret`，點擊 **Close** 關閉
    
    在左邊的 `Input your own scopes` 欄位輸入 `https://www.googleapis.com/auth/chromewebstore` 並點擊 **Authorize APIs**
    
    登入後，點擊 `Exchange authorization code for tokens` 即可取得所有需要的 Token 資訊！
    
    點擊左側的 **Step 2 Exchange authorization code for tokens** 就可以看到 `Refresh token` 與 `Access token` 了！
    
    > 若要透過 Postman 取得 Refresh Token 可以參考我的 [如何讓 Google 的 OAuth 2.0 發出 Refresh Token](/post/2023/06/14/How-to-issue-Refresh-Token-in-Google-OAuth) 文章。
    

詳細的步驟都已經寫在 [使用 Chrome Web Store 發佈 API](https://developer.chrome.com/docs/webstore/using-api) 的官方文件中，等這些資訊都拿到後，就可以透過 REST API 發佈 Chrome Extension 的新版本了！

### 設定 GitHub Secrets

你必須先啟用 `secrets.GITHUB_TOKEN` 的權限，設定的步驟如下：

1.  進入您的 GitHub 專案頁面，點選 `Settings` 頁籤
2.  在左側選單中選擇 `Actions` > `General`
3.  找到 `Workflow permissions` 並切換到 `Read and write permissions` 選項

然後到 `Secrets and variables > Actions` 設定一些自訂變數，設定的步驟如下：

1.  進入您的 GitHub 專案頁面，點選 `Settings` 頁籤
2.  在左側選單中選擇 `Actions` > `General`
3.  在左側選單中選擇 `Secrets and variables` > `Actions`

我的 GitHub Actions YAML 需要額外設定 4 個秘密變數，你可以點選 `New repository secret` 來新增以下秘密變數：

1.  `EXTENSION_ID`: 你的 Chrome 擴充套件 ID
2.  `CLIENT_ID`: OAuth 2.0 用戶端 ID
3.  `CLIENT_SECRET`: OAuth 2.0 用戶端密鑰
4.  `REFRESH_TOKEN`: 用於獲取新的存取權杖的 Refresh Token (刷新金鑰)

### 發行到另一個 Repo

如果你需要將 Release 發行到另一個 Repo 的話，可以參考 [softprops/action-gh-release](https://github.com/softprops/action-gh-release) 相關說明。

你可以額外建立一個名叫 `GH_TOKEN` 的秘密變數，用於身份驗證 GitHub API 的金鑰，請到 [Fine-grained personal access tokens](https://github.com/settings/personal-access-tokens) 頁面建立一個新的 Token，並給予 `repo` 權限。其中 `Repository permissions` 請選擇 `Contents` 的 `Read and write`。

以下是 YAML 設定範例：

```yaml
name: Main

on: push

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

      - name: Generate Changelog
        run: echo "# Good things have arrived" > ${{ github.workspace }}-CHANGELOG.txt

      - name: Release
        uses: softprops/action-gh-release@v2
        if: startsWith(github.ref, 'refs/tags/')
        with:
          body_path: ${{ github.workspace }}-CHANGELOG.txt
          repository: my_gh_org/my_gh_repo
          # note you'll typically need to create a personal access token
          # with permissions to create releases in the other repo
          token: ${{ secrets.GH_TOKEN }}
```

### 相關連結

-   [Use the Chrome Web Store Publish API](https://developer.chrome.com/docs/webstore/using-api)
-   [Chrome Web Store API Reference](https://developer.chrome.com/docs/webstore/api)
-   [OAuth 2.0 Playground | Google Developers](https://developers.google.com/oauthplayground/)
-   [upload error - You will need to go to the Chrome Web Store Developer Dashboard and upload it manually. #27](https://github.com/mnao305/chrome-extension-upload/issues/27)
-   [softprops/action-gh-release: 📦 GitHub Action for creating GitHub Releases](https://github.com/softprops/action-gh-release)
-   [GitHub Actions: Control permissions for GITHUB\_TOKEN](https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/)
-   [Automatic token authentication - GitHub Docs](https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
