CI/CD 實戰指南(四):使用 Google Apps Script Web App 串接 GitHub Actions 建置免費易用的打包工具平台
GAS Web App 串接 GitHub, Slack, Firebase 或 Asana/Jira API,建置中繼站,提供跨團隊共用的打包工具平台
ℹ️ℹ️ℹ️ Click here to view the English version of this article, translated by OpenAI.
CI/CD 實戰指南(四):使用 Google Apps Script Web App 串接 GitHub Actions 建置免費易用的打包工具平台
GAS Web App 串接 GitHub, Slack, Firebase 或 Asana/Jira API,建置中繼站,提供跨團隊共用的打包工具平台
Photo by Lee Campbell
前言
前篇「 CI/CD 實戰指南(三):使用 GitHub Actions 實作 App iOS CI 與 CD 工作流程 」我們已經完成了 App iOS 專案的 CI/CD 底層功能建設,現在已經可以 CI 自動測試驗證跟 CD 打包部署;然而在實務產品開發流程上, 打包部署工作多半是為了後續交付給其他職能夥伴 進 行 QA(Quality Assurance) 功能驗證,這時候 CD 場景就不在只侷限在工程端了,可能跨足 QA、PM、設計(Design QA) 甚至是老闆想先玩看看。
GitHub Actions workflow_dispatch
手動表單觸發事件,雖然能提供簡易的表單讓使用者操作打包,但如果操作的對象是非工程人員就相對很不友善,他們不懂:什麼是分支?欄位要不要填?怎麼知道打包好了?好了怎麼下載?…etc 另外還有權限控管問題,如果要讓其他職能夥伴直接使用 GitHub Actions 打包,就需要把他的帳號加到 Repo 當中, 資安控管上很不安全也不合理 ,只是要操作打包表單卻要開整個 Source Code 給他。
不同於 Jenkins 有獨立的 Web 工具平台,GitHub Actions 就只有這個功能。
workflow_dispatch 的表單樣式
因此 我們需要一個中繼站打包平台,用來服務其他職能使用者 ,整合 Asana/Jira 的任務單,讓使用的人可以直接用任務單打包 App,並且直接在上面查看進度、下載打包結果。
GAS Web App 中繼站
上一篇 關注的是右邊核心 GitHub Actions CI/CD 工作流程開發;本文關注的是左半部 to End-User 打包工具平台、增加使用者體驗的部分。
Google Apps Script — Web App 打包工具平台 成果圖
- 打包表單: 整合專案管理工具撈取單號、整合 GitHub 撈取開啟中的 Pull Request
- 把包紀錄: 顯示打包歷史紀錄、正在打包的任務進度狀態、點擊取得下載連結撈取 Firebase App Distribution 下載連結、資訊
- Runner 狀態: 顯示 Self-hosted Runner 狀態。
- Slack 打包進度通知。
- 支援手機版
- 支援限制組織團隊內帳號使用
主要職責
0 狀態、0 資料庫, 單純為中繼交換站 ,整合顯示各個 API 資料 (e.g. Asana/Jira/GitHub)、轉發表單請求到 GitHub Actions。
操作要求: 支援手機與電腦。
權限要求: 能限制只有團隊組織成員能存取。
Online Demo Web App
Sign in Edit description script.google.com
技術選擇
第一篇文章 有提到過,這邊再詳細整理一次。
整合進 Slack
我們嘗試過讓 Slack 擔任打包平台,自行開發後端服務並架設在 GCP 上,與 Slack API、Asana API 進行串接整合,再將 Slack 送來的表單轉手發送給 GitHub API 觸發後續 GitHub Actions;體驗上非常爽而且統一在團隊的協作工具上,可以無痛使用; 缺點是開發及後續維護成本很高 ,因為是自己用 Ktor 開發的後端服務,需要 App 工程師同時兼做後端、處理 Google 服務 OAuth 整合問題、部分功能可能在這做掉(例如:送審)…功能複雜;如果後來的新人沒銜接好這整塊幾乎沒辦法維護下去了,另外每個月還要花 $15 USD GCP 伺服器費用。
初期也嘗試過用 FaaS 服務串接 Slack API,例如 Cloud Funtions,但是 Slack API 要求串接的服務要能在 3 秒內回應請求,否則視為失敗;FaaS 會有 冷啟動問題 ,當服務一段時間沒呼叫就會進入休眠,再次呼叫時會需要較長時間才能響應 (≥ 5秒),會造成 Slack 打包表單很不穩定,時常出現 Timeout Error。
整合進內部系統
這當然是最優解,如果團隊有 Web、後端人力,直接與現有系統整合是最好也最安全的。
本文的前提是:沒有,App 自立自強。
Google Apps Script — Web App
Google Apps Script 是我們的老夥伴,之前做過很多 RPA 專案都是用他去做排程觸發執行任務,例如:「 Crashlytics + Google Analytics 自動查詢 App Crash-Free Users Rate 」、「 使用 Google Apps Script 實現每日數據報表 RPA 自動化 」,這時候我又想起了它;GAS 有一個功能是部署成 Web (App) 直接當網頁服務。
Google Apps Script 優點:
- ✅ 免費、 正常使用下幾乎摸不到上限
- ✅ Functions as a Service 方法即服務,不需自行架設維護伺服器
- ✅ 權限控管同 Google Workspace,可設定僅限組織內 Google 帳號使用
- ✅ 無痛整合 Google 生態系相關服務(e.g. Firebase, GA…etc)與資料 (不用自己做 OAuth)
- ✅ 程式語言使用 JavaScript 易上手 (V8 Runtime 支援 ES6+ )
- ✅ 快速撰寫、快速上線、快速使用
- ✅ 服務穩定、長久 (已推出超過 16 年)
- ✅ AI Can Help! 實測使用 ChatGPT 輔助開發,準確率可達 95%
Google Apps Script 缺點:
- ❌ 內建的版本管控一言難盡
- ❌ 內建不支援檔案、資料儲存、金鑰/憑證管理
- ❌ Web App 無法做到 100% 體驗的 RWD
- ❌ 專案只能綁在個人帳號而不是組織
- ❌ 雖然 Google 有持續在開發維護,但整體功能更新緩慢
- ❌ 網路請求
UrlFetchApp
不支援設定 User-Agent - ❌ Web App
doGet
/doPost
不支援取得 Headers 資訊 - ❌ FaaS 冷啟動問題
- ❌ 不支援多人同時開發 但在 Web App 影響不大,頂多要多等幾秒才進入網頁。
以上是 GAS 本身服務的優缺點,對做打包工具 Web 影響不大;選擇使用此方案對比 Slack 方案,更快速、輕量跟容易交接上手, 缺點是團隊需要多知道這個工具的網址在哪跟怎麼用、還有因 GAS 程式庫功能有限 (e.g. 無內建加密演算法庫) 基本上只能做純中繼平台,例如送審,那也只能轉發送審請求請 GitHub Actions 做。
另外也僅適用是 Google Workspace 工作環境的團隊; 權衡了資源與需求,因此採用 Google Apps Script — Web App 來實現打包工具平台。
UI Framework
我們直接使用 Bootstrap CDN,不然要自己弄 CSS 樣式太麻煩了,Bootstrap 問 AI 怎麼組合使用也更準確方便。
動手做
這邊已經把整個平台架構開源了,大家可以依照自己團隊的需求基於這個版本再客製化即可。
開源範例專案
在 GAS 上直接檢視專案:
Google Drive: Sign-in Access Google Drive with a Google account (for personal use) or Google Workspace account (for business use) . script.google.com
GitHub Repo 備份:
檔案架構
寫了一個很陽春的 類-MVC 架構,如要調整或不知道功能問 AI 都能得到準確答案。
系統
- appsscript.json: GAS 系統的 Metadata 設定檔案 重點是「oauthScopes」變數,宣告這個 Script 會用到的外部權限。
- Entrypoint.gs: 定義 doGet( ) 進入點
Controller
- Controller_iOS.gs: iOS 打包工具頁面 Controller,負責撈數據給 View 顯示
View
- View_index.html: 整個打包工具骨架、首頁
- View_iOS.html: iOS 打包工具頁面骨架
- View_iOS_Runs.html: iOS 打包工具 — 打包紀錄內容頁面
- View_iOS_Form.html: iOS 打包工具 —打包表單頁面
- View_iOS_Runners.html: iOS 打包工具 — Self-hosted Runner 狀態頁面
Model(Lib)
- Credentials.gs: 定義金鑰內容 (⚠️️️請注意️,GAS 如果要走 GCP IAM 會蠻複雜的因此我們直接定義金鑰在此, 所以這個 GAS 專案會包含機密資訊,請不要隨意共享專案檢視編輯權限 )
- StubData.gs: Online Demo 用的 Stub 方法、資料。
- Settings.gs: 一些常用設定,跟 lib init。
- GitHub.gs: GitHub API 操作封裝。
- Slack.gs: Slack API 操作封裝。
- Firebase.gs: Firebase — App Distribution API 操作封裝。
建立自己的打包平台
1.建立一個 Google Apps Script 專案 、命名
到專案設定 → 勾選「在編輯器中顯示「appsscript.json」資訊清單檔案」才會出現「appsscript.json」Metadata 檔案。
2.參考我的 開源專案檔案 ,把所有檔案按照範例建立好跟把內容無腦複製過來
很蠢,但沒辦法。
StubData.gs 先一起複製過來,第一次部署測試可以使用。
另一個方法是用 clasp (Google Apps Script CLI) git clone demo project 後把 Code 推上去。
複製完長得會跟範例專案一模一樣。
3. 首次部署「網頁應用程式」查看結果
專案右上角「部署」 → 「新增部署作業」 → 類型「網頁應用程式」:
執行身份:
- 我
統一都用你的帳號身分執行腳本。
- 存取網頁程式的使用者
會以當前登入的 Google 帳號使用者身份執行腳本。
誰可以存取:
- 只有我自己
- XXX 同個組織中的所有使用者
只有同組織+已登入的 Google 帳號使用者可以存取。
- 所有已登入 Google 帳號的使用者
已登入的 Google 帳號使用者都可以存取。
- 所有人
不需要登入 Google 帳號、所有人都可以公開存取。
如果是內部工具:可以選擇「 誰可以存取:XXX 同個組織中的所有使用者」+「執行身份:存取網頁程式的使用者」進行安全控管。
完成部署後的「網頁應用程式」網址,就是你的 Web App 打包工具網址,可以分享給團隊成員使用。(網址很醜,可以自己用短網址服務包裝一下、 更新部署內容不會異動網址 )
使用者首次使用需要同意授權
首次點擊 Web App 網址,需要先同意授權。
- Review Permission → 選擇要使用此 Web App 的帳戶身份
- 未驗證警告視窗,點擊「進階」展開 → 點擊 前往「XXX」(不安全)
- 點擊「允許」
爾後如果腳本權限沒更動不用重新授權。
完成授權同意後就會進入打包工具首頁:
Demo 打包工具部署成功 🎉🎉🎉
註:「這個應用程式是由 Google Apps Script 的使用者建立」這個提示無法自動隱欌。
更新部署
⚠️所有程式碼的變動都需要更新部署才會生效。
️️⚠️所有程式碼的變動都需要更新部署才會生效。
⚠️所有程式碼的變動都需要更新部署才會生效。
這邊要注意程式碼變動儲存完不會直接變動到 Web App 上,所以發現重整沒效果就是這個原因; 需要到「部署」 → 「管理部署作業」→「編輯」→ 版本「建立新版本」 → 點擊「部署」 → 「完成」 。
更新部署完再重整網頁就能看到改動生效。
新增測試部署方便開發
如同前述,所有改動要生效都要更新部署;這在開發階段非常麻煩,因此在開發階段我們可以用「測試部署」來快速驗證改動是否正確。
到「部署」 → 「測試部署作業」 → 取得測試用「網頁應用程式」網址。
在開發階段我們直接使用此網址就能儲存,檔案更改儲存完,回到這個開發用網址重整網頁就能看到成果!
都開發好之後再照前文說的更新部署,釋出給使用者使用。
修改 Demo 範例專案串接真實資料
再來才是重點,串接真實資料,GitHub Actions Workflow 參考的是 上一篇文章中建立好的 CI/CD 流程 ,你也可依照實際的 Actions Workflow 調整參數。
⚠️修改前請注意
Google Apps Script 平台並不能很好的支援多人或甚至多開視窗開發,我自己踩過的雷是不小心開了兩個編輯視窗,在 A 編輯完,後來在 B 編輯,所以修改都被 B 的舊版覆蓋掉了;因此 建議同時間只有一個人一個視窗在編輯 Script 。
GitHub 串接
帶入 GitHub API Token:
GitHub -> 帳號 -> Settings -> Developer Settings -> Fine-grained personal access tokens or Personal access tokens (classic)。
建議使用 Fine-grained personal access tokens 比較安全(但有期限)。
Fine-grained personal access tokens 需要的權限如下:
- Repo: 記得選要操作的 Repo
- Permissions:
Actions (read/write)
、Administration (read only)
如果不想依賴在某人的帳號上建議建立一支乾淨的團隊 GitHub 帳號,使用它的 Token。
到 GAS 專案 → Credentials.gs
→ 將 Token 帶入到 githubToken
變數中。
替換 GithubStub 成 GitHub:
到 GAS 專案 → Settings.gs
→ 將:
1
const iOSGitHub = new GitHubStub(githubToken, iOSRepoPath);
改成
1
const iOSGitHub = new GitHub(githubToken, iOSRepoPath);
儲存檔案。
重新整理測試用「網頁應用程式」網址查看改動是否正確:
能正確顯示資料代表: GitHub 改串真實資料成功 🎉🎉🎉
也能順手切到「Runner 狀態」查看 Self-hosted Runner 狀態撈取正不正常:
註:我 Runner 沒開…所以是離線中。
Slack 串接
為了串接 Slack 通知,我們首先要回到 Repo → GitHub Actions 新增一個包裹打包 Action Workflow 的通知容器 Action。
CD-Deploy-Form.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# Workflow(Action) 名稱
name: CD-Deploy-Form
# Actions Log 的標題名稱
run-name: "[CD-Deploy-Form] ${{ github.ref }}"
# 同個 Concurrency Group 如果有新的 Job 會取消正在跑的
# 例如 重複觸發相同分支的打包任務,會取消前一個任務
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# 觸發事件
on:
# 手動表單觸發
workflow_dispatch:
# 表單 Inputs 欄位
inputs:
# App 版本號
VERSION_NUMBER:
description: 'Version Number of the app (e.g., 1.0.0). Auto-detect from the Xcode project if left blank.'
required: false
type: string
# App Build Number
BUILD_NUMBER:
description: 'Build number of the app (e.g., 1). Will use a timestamp if left blank.'
required: false
type: string
# App Release Note
RELEASE_NOTE:
description: 'Release notes of the deployment.'
required: false
type: string
# 觸發者的 Slack User ID
SLACK_USER_ID:
description: 'Slack user id.'
required: true
type: string
# 觸發者的 Email
AUTHOR:
description: 'Trigger author email.'
required: true
type: string
# Job 工作項目
jobs:
# 開始打包時傳送 Slack 訊息
# Job ID
start-message:
# 小工作直接用 GitHub Hosted Runner 跑,用量不大
runs-on: ubuntu-latest
# 設定最長 Timeout 時間,防止異常情況發生時無止盡的等待
# 正常情況不可能跑超過 5 分鐘
timeout-minutes: 5
# 工作步驟
steps:
- name: Post a Start Slack Message
id: slack
uses: slackapi/slack-github-action@v2.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ inputs.SLACK_USER_ID }}
text: "已收到打包請求。\nID: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.run_id }}>\nBranch: ${{ github.ref_name }}\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"
# Job Output 給後續 Job 使用
# ts = Slack 訊息 ID,後續通知才可以 Reply 在同個 Threads
outputs:
ts: ${{ steps.slack.outputs.ts }}
deploy:
# Job 預設是並發執行,用 needs 限制需等待 start-message 完成才執行
# 執行打包部署任務
needs: start-message
uses: ./.github/workflows/CD-Deploy.yml
secrets: inherit
with:
VERSION_NUMBER: ${{ inputs.VERSION_NUMBER }}
BUILD_NUMBER: ${{ inputs.BUILD_NUMBER }}
RELEASE_NOTE: ${{ inputs.RELEASE_NOTE }}
AUTHOR: ${{ inputs.AUTHOR }}
# 打包部署任務成功訊息
end-message-success:
needs: [start-message, deploy]
if: ${{ needs.deploy.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Post a Success Slack Message
uses: slackapi/slack-github-action@v2.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ inputs.SLACK_USER_ID }}
thread_ts: "${{ needs.start-message.outputs.ts }}"
text: "✅ 打包部署成功。\n\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"
# 打包部署任務失敗訊息
end-message-failure:
needs: [deploy, start-message]
if: ${{ needs.deploy.result == 'failure' }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Post a Failure Slack Message
uses: slackapi/slack-github-action@v2.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ inputs.SLACK_USER_ID }}
thread_ts: "${{ needs.start-message.outputs.ts }}"
text: "❌ 打包部署失敗,請檢查執行狀況結果或稍後再試。\n\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"
# 打包部署任務取消訊息
end-message-cancelled:
needs: [deploy, start-message]
if: ${{ needs.deploy.result == 'cancelled' }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Post a Cancelled Slack Message
uses: slackapi/slack-github-action@v2.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ inputs.SLACK_USER_ID }}
thread_ts: "${{ needs.start-message.outputs.ts }}"
text: ":black_square_for_stop: 打包部署已取消。\n\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"
完整程式碼: CD-Deploy-Form.yml
這個 Action 只是個容器,串接 Slack 通知,實際是復用 上一篇文章 中寫好的 CD-Deploy.yml Action。
- Slack Bot App 建立、發訊息權限設定可參考我 之前的文章
- 記得到 Repo → Secrets 新增對應的
SLACK_BOT_TOKEN
跟帶入 Slack Bot App Token 值
回到 GAS 專案 → Credentials.gs
→ 將 Token 帶入到 slackBotToken
變數中。
再到 GAS 專案 → Settings.gs
→ 將:
1
const slack = new SlackStub(slackBotToken);
改成
1
const slack = new Slack(slackBotToken);
儲存檔案。
如果你沒有現成的 Slack Bot App 可發通知也懶得建立,可以忽略這裡的所有步驟,並刪除 GAS 專案中有關 slack 的使用。
GitHub 串接 — 打包表單
到 GAS 專案 → Controller_iOS.gs
→ 調整 View_iOS_Form.html
內容: 移除假 Asana Tasks 串接方法:
<? tasks.forEach(function(task) { ?>
<option value="<?=task.githubBranch?>">[<?=task.id?>] <?=task.title?></option>
<? }) ?>
也可以在這邊自己調整預設分支(這邊是 main
)。
—
到 GAS 專案 → Controller_iOS.gs
→ 調整 iOSLoadForm()
內容:
- 移除
template.tasks = Stubable.fetchStubAsanaTasks();
這行假串接 Asana 的方法。 如果要串 Asana/Jira 可以直接問 ChatGPT 請他幫忙產串接方法。 template.prs = iOSGitHub.fetchOpenPRs();
是真的串 GitHub API 拿 Opened PR List,可依需求保留。
送出後的處理 在 iOSSubmitForm()
內容:
可依照實際 GitHub Actions Workflow 檔案名稱、 workflow_dispatch
inputs 欄位參數進行調整:
1
2
3
4
5
6
7
8
iOSGitHub.dispatchWorkflow("CD-Deploy-Form.yml", branch, {
"BUILD_NUMBER": buildNumber,
"VERSION_NUMBER": versionNumber,
"VERSION_NUMBER": versionNumber,
"RELEASE_NOTE": releaseNote,
"AUTHOR": email,
"SLACK_USER_ID": slack.fetchUserID(email)
});
也可以加入自己的必填條件驗證,這邊只驗證一定要填分支,不然會跳錯誤訊息。
如果認為這樣不夠安全可以自己再加入密碼驗證或只有特殊帳號可以使用。
最後一行 Slack 通知功能需要設定好 Slack ,如果沒有 Slack Bot App 或懶得串接 Slack,在 Demo Actions Repo 中可以直接改用
iOSGitHub.dispatchWorkflow("CD-Deploy.yml")
然後移除掉SLACK_USER_ID
參數即可。
重新整理測試用「網頁應用程式」網址查看改動是否正確:
可以看到打包表單就只剩 Opened PR List 了。
填好資料按「送出請求」測試看看打包表單:
提示送出成功代表沒問題,回到打包紀錄也能看到任務開始執行了 🎉 :
重複點打包紀錄能更新進度。
常見送出錯誤:
Required input ‘SLACK_USER_ID’ not provided
: GitHub Actions 這個SLACK_USER_ID 欄位為必填,但沒有帶,可能是 Slack 設定失敗、當前 User Email 找不到對應的 Slack UID。
Workflow does not have ‘workflow_dispatch’ trigger
、分支過舊,請更新 xxx 分支
: 選擇的分支找不到對應的 Action Workflow 檔案(iOSGitHub.dispatchWorkflow 指定的檔案)。
No ref found for
、找不到分支
: 找不到此分支。
Firebase App Distribution — 取得下載連結串接
最後一個小功能是串接 Firebase App Distribution 直接取得下載資訊跟連結,方便在手機上開啟打包平台工具點擊直接下載安裝。
之前介紹過「 Google Apps Script x Google APIs 快速串接整合方式 」GAS 能快速無痛整合 Firebase。
串接原理
在串接之前先講一下這個「Tricky」的串接原理。
我們的打包平台是沒有資料庫的,純做 API 中繼站;所以實際上是我們在 GitHub Acitons CD-Deploy.yml 打包作業時,把 Job Run ID 帶入到 Release Note (當然也可以帶到 Build Number):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ID="${{ github.run_id }}" // Job Run ID
COMMIT_SHA="${{ github.sha }}"
BRANCH_NAME="${{ github.ref_name }}"
AUTHOR="${{ env.AUTHOR }}"
# 組合 Release Note
RELEASE_NOTE="${{ env.RELEASE_NOTE }}
ID: ${ID}
Commit SHA: ${COMMIT_SHA}
Branch: ${BRANCH_NAME}
Author: ${AUTHOR}
"
# 執行 Fastlane 打包&部署 Lane
bundle exec fastlane beta release_notes:"${RELEASE_NOTE}" version_number:"${VERSION_NUMBER}" build_number:"${BUILD_NUMBER}"
這樣 Firebase App Distribution Release Notes 就會有 Job Run ID。
GAS Web App 打包工具平台會串 GitHub API 取得 GitHub Actions 執行紀錄我們直接用 API 給的 Job Run ID 帶到 Firebase App Distribution API 查詢 Release Notes 中有包含 *ID: XXX*
的版本,就能找到對應的打包紀錄了。
不用任何資料庫也能做到兩個工具平台的對應。
串接專案設定
到 GAS → 專案設定 → Google Cloud Platform (GCP) 專案 → 變更專案:
輸入想串接的 Firebase 專案編號。
初次設定可能會跳錯誤 「如要變更專案,請設定 OAuth 同意畫面。設定 OAuth 同意畫面詳細資料。」沒有的話可跳過以下步驟。
點擊「OAuth 同意畫面詳細資料」連結 → 點擊「設定同意畫面」:
點擊「開始」:
應用程式資訊:
- 應用程式名稱:
輸入你的工具名稱
- 使用者支援電子郵件:
選擇電子郵件
目標對象:
- 內部:僅限組織內部夥伴使用
- 外部:所有 Google 帳戶使用者都能同意授權後使用
聯絡資訊:
- 輸入接收通知用電子郵件
勾選同意《 Google API 服務:使用者資料政策 》。
最後點擊「 建立 」。
—
回到 GAS → 專案設定 → Google Cloud Platform (GCP) 專案 → 變更專案:
再次輸入 Firebase 專案編號點擊「變更專案」。
沒出現錯誤就是完成綁定了。
—
如果選擇的是「外部」可能還需要完成以下設定:
點擊「專案編號」 → 展開左側欄 → 「API 和服務」→ 「OAuth 同意畫面」
選擇「目標對象」 → 測試 點擊「發布應用程式」→ 完成。
使用者就能照前文的「 使用者首次使用需要同意授權 」步驟完成授權就能使用了!
如果上述步驟沒設定,使用者會遇到以下錯誤:
已封鎖存取權「XXX」未完成 Google 驗證程序
— — —
串接專案
回到串接上,Firebase 是直接用 ScriptApp.getOAuthToken()
依照執行身份動態取得 Token 使用,因此不需要設定 Token。
只需要到 GAS 專案 → Settings.gs
→ 將:
1
const iOSFirebase = new FirebaseStub(iOSFirebaseProject);
改成
1
const iOSFirebase = new Firebase(iOSFirebaseProject);
即可。
重新整理測試用「網頁應用程式」網址到打包紀錄 → 找一筆紀錄點擊「取得下載連結」:
如果有在 Firebase App Distribution Release Notes 找到對應的 Job Run Id 打包,就會直接顯示下載資訊跟點下載能直接導到下載頁。
完成!🎉🎉🎉
成果
至此你已經把範例都改成您的實際可用的打包工具,剩下的客製化功能、更多第三方 API 串接、更多表單可以自行延伸 (跟 ChatGPT 討論)。
最後不要忘記都開發好測試完畢要上線,需要照前文步驟 — 更新部署作業才會生效喔!
串接延伸
延續我們「中繼站」角色的精神,這邊提供幾個快速串接的 CheatSheet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function asanaAPI(endPoint, method = "GET", data = null) {
var options = {
"method" : method,
"headers": {
"Authorization": "Bearer "+asanaToken
},
"payload" : data
};
var url = "https://app.asana.com/api/1.0"+endPoint;
var res = UrlFetchApp.fetch(url, options);
var data = JSON.parse(res.getContentText());
return data;
}
asanaAPI("/projects/{project_gid}/tasks")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// jql = 篩選條件
function jiraTickets(jql) {
const url = `https://xxx.atlassian.net/rest/api/3/search`;
const maxResults = 100;
let allIssues = [];
let startAt = 0;
let total = 0;
do {
const queryParams = {
jql: jql,
startAt: startAt,
maxResults: maxResults,
fields: "assignee,summary,status"
};
const queryString = Object.keys(queryParams)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`)
.join("&");
const options = {
method: "get",
headers: {
Authorization: "Basic " + jiraToken,
"Content-Type": "application/json",
},
muteHttpExceptions: true,
};
const response = UrlFetchApp.fetch(`${url}?${queryString}`, options);
const json = JSON.parse(response.getContentText());
if (response.getResponseCode() != 200) {
throw new Error("Failed to fetch Jira issues.");
}
if (json.issues && json.issues.length > 0) {
allIssues = allIssues.concat(json.issues);
total = json.total;
startAt += json.issues.length;
} else {
break;
}
} while (startAt < total);
var groupIssues = {};
for(var i = 0; i < allIssues.length; i++) {
const issue = allIssues[i];
if (groupIssues[issue.fields.status.name] == null) {
groupIssues[issue.fields.status.name] = [];
}
groupIssues[issue.fields.status.name].push(issue);
}
return groupIssues;
}
jiraTickets(`project IN(App)`);
如果真的需要資料庫,可以使用 Google Sheet 代替:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Saveable {
constructor(type) {
// https://docs.google.com/spreadsheets/d/Sheet-ID/edit
const spreadsheet = SpreadsheetApp.openById("Sheet-ID");
this.sheet = spreadsheet.getSheetByName("Data"); // Sheet Name
this.type = type;
}
write(key, value) {
this.sheet.appendRow([
this.type,
key,
JSON.stringify(value)
]);
}
read(key) {
const data = this.sheet.getDataRange().getValues();
const row = data.find(r => r[0] === this.type && r[1] === key);
if (row) {
return JSON.parse(row[2]);
}
return null;
}
}
let saveable = Saveable("user");
// Write
saveable.write("birthday_zhgchgli", "0718");
// Read
saveable.read("birthday_zhgchgli"); // -> 0718
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function slackSendMessage(channel, text = "", blocks = null) {
const content = {
channel: channel,
unfurl_links: false,
unfurl_media: false,
text: text,
blocks: blocks
};
try {
const response = slackRequest("chat.postMessage", content);
return response;
} catch (error) {
throw new Error(`Failed to send Slack message: ${error}`);
}
}
function slackRequest(path, content) {
const options = {
method: "post",
contentType: "application/json",
headers: {
Authorization: `Bearer ${slackBotToken}`,
'X-Slack-No-Retry': 1
},
payload: JSON.stringify(content)
};
try {
const response = UrlFetchApp.fetch("https://slack.com/api/"+path, options);
const responseData = JSON.parse(response.getContentText());
if (responseData.ok) {
return responseData
} else {
throw new Error(`Slack: ${responseData.error}`);
}
} catch (error) {
throw error;
}
}
更多 Google Apps Script 案例:
- Google Apps Script x Google APIs 快速串接整合方式
- 簡單 3 步驟 — 打造免費 GA4 自動數據通知機器人
- 使用 Google Apps Script 實現每日數據報表 RPA 自動化
- Slack & ChatGPT Integration
- 使用 Google Apps Script 三步驟免費建立 Github Repo Star Notifier
- Crashlytics + Google Analytics 自動查詢 App Crash-Free Users Rate
- Crashlytics + Big Query 打造更即時便利的 Crash 追蹤工具
總結
感謝您的耐心閱讀與參與,CI/CD 從 0 到 1 系列文章到此告一段落;希望能實際幫助到您與您的團隊建置完善的 CI/CD 工作流程,提升效率與產品穩定性;有任何實作問題歡迎留言討論,這四篇文章大約花了 14+ 天撰寫, 如果您覺得不錯歡迎 Follow 我的 Medium 和與朋友同事分享 。
謝謝。
Buy me a coffee
本系列文章花費了大量的時間精力撰寫,如果內容對您有幫助、對您的團隊有實質提升工作效率與產品品質;歡迎請我喝杯咖啡,感謝支持!
系列文章:
- CI/CD 實戰指南(一):CI/CD 是什麼?如何透過 CI/CD 打造穩定高效的開發團隊?工具選擇?
- CI/CD 實戰指南(二):GitHub Actions 與 self-hosted Runner 使用與建置大全
- CI/CD 實戰指南(三):使用 GitHub Actions 實作 App 專案的 CI 與 CD 工作流程
- CI/CD 實戰指南(四):使用 Google Apps Script Web App 串接 GitHub Actions 建置免費易用的打包工具平台
有任何問題及指教歡迎 與我聯絡 。
本文首次發表於 Medium ➡️ 前往查看
由 ZMediumToMarkdown 與 Medium-to-jekyll-starter 提供自動轉換與同步技術。