CI/CD 実践ガイド(4):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(品質保証)機能検証を行うためのものです。このため、CD の利用シーンはエンジニアだけに限らず、QA、PM、デザイン(Design QA)、さらには経営者が先に試したい場合などにも及びます。
GitHub Actions の workflow_dispatch 手動フォームトリガーは、簡単なフォームを提供してユーザーがビルド操作を行えますが、対象が非エンジニアの場合は非常に使いづらいです。彼らは「ブランチとは何か?」「項目は入力すべきか?」「ビルドが完了したかどうかはどう確認する?」「完了後はどうやってダウンロードするのか?」など分かりません。
また、権限管理の問題もあります。別の職能メンバーに直接 GitHub Actions でビルドさせる場合、そのメンバーのアカウントをリポジトリに追加しなければなりません。セキュリティ管理上非常に危険かつ不合理であり、単にビルドフォームを操作するだけなのにソースコード全体を見せる必要があります。
Jenkinsとは異なり、GitHub Actionsには独立したWebツールプラットフォームがありません。

workflow_dispatch のフォームスタイル
したがって、他の職能ユーザーにサービスを提供するための中継ステーションとしてのパッケージングプラットフォームが必要です。Asana/Jiraのタスクを統合し、ユーザーがタスクから直接アプリをパッケージングでき、進捗確認やパッケージ結果のダウンロードもその場で行えます。

GAS Web App 中継ステーション
上一篇 は右側のコアである GitHub Actions CI/CD ワークフローの開発に焦点を当てています。この記事は左側のエンドユーザー向けのパッケージングツールプラットフォームとユーザー体験の向上に注目しています。
Google Apps Script — Web App パッケージングツールプラットフォーム 成果図







-
パッケージフォーム: プロジェクト管理ツールからチケット番号を取得し、GitHubからオープン中のプルリクエストを取得する連携
-
パッケージ記録: パッケージの履歴を記録し、進行中のパッケージタスクの進捗状況を表示。クリックで Firebase App Distribution のダウンロードリンクを取得し、情報を表示
-
Runner 状態: Self-hosted Runner の状態を表示
-
Slack へのパッケージ進行状況通知
-
モバイル対応
-
組織チーム内のアカウント使用制限をサポート
主な職務内容
0 ステータス、0 データベース、単純な中継交換所として、各種 API のデータ(例:Asana/Jira/GitHub)を統合表示し、フォームリクエストを GitHub Actions に転送します。
操作要件: モバイルとPCに対応。
権限要件: チーム組織のメンバーのみアクセス可能に制限できること。
オンラインデモ Web アプリ
- 初めてご利用の場合は、下の図の認証を参照してください(デモアプリ専用):

プロジェクトソースコード: https://script.google.com/home/projects/1CBB39OMedqP9Ro1WSlvgDnMBin4-ksyhgly2h_KrbOuFiPHTalNgwHOp/edit
技術選択
第一篇記事で触れましたが、ここで改めて詳しくまとめます。
Slack への統合
私たちは Slack をパッケージングプラットフォームとして試み、自前でバックエンドサービスを開発し GCP 上に構築しました。Slack API、Asana API と連携し、Slack から送られたフォームを GitHub API に転送して GitHub Actions をトリガーする仕組みです。体験としては非常に快適でチームのコラボレーションツールに統一でき、スムーズに使えました。しかし、欠点は開発およびその後の保守コストが非常に高いことです。Ktor で開発したバックエンドサービスのため、アプリエンジニアがバックエンドも兼任し、Google サービスの OAuth 統合問題を処理し、一部機能(例えば審査送信など)もここで実装する必要があり、機能は複雑でした。後から入った新人が引き継げなければほぼ保守不可能になり、さらに毎月 $15 USD の GCP サーバー費用もかかります。
初期は Cloud Functions などの FaaS サービスを使って Slack API と連携しようとしましたが、Slack API は3秒以内に応答しないと失敗とみなされます。FaaS にはコールドスタート問題があり、一定期間呼び出されないと休止状態になり、再度呼び出すと応答に長時間(≥ 5秒)かかるため、Slack のパッケージフォームが非常に不安定になり、頻繁にタイムアウトエラーが発生しました。
内部システムへの統合
これはもちろん最適解であり、チームにWebやバックエンドの人材がいる場合は、既存のシステムと直接統合するのが最も良く、かつ安全です。
この記事の前提は:なし、アプリは自立自強。
Google Apps Script — Web アプリ
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エコシステム関連サービス(例:Firebase、GAなど)やデータとの無痛統合(OAuth不要)
-
✅ プログラミング言語は JavaScript を使用し、習得が容易(V8 Runtime は ES6+ をサポート)
-
✅ 迅速な作成、迅速な公開、迅速な利用
-
✅ サービスは安定しており、長期間(16年以上)提供されています
-
✅ AIが助けになる!ChatGPTを活用した開発で、正確率は95%に達することを実証
Google Apps Script の欠点:
-
❌ 内蔵のバージョン管理は一言で説明できません
-
❌ ファイル、データ保存、キー/証明書管理は標準でサポートされていません
-
❌ Web App は100%のレスポンシブデザイン(RWD)体験を実現できません
-
❌ プロジェクトは個人アカウントにのみ紐付け可能で、組織には紐付けられません
-
❌ Googleは継続的に開発と保守を行っていますが、全体の機能更新は遅いです
-
❌ ネットワークリクエスト
UrlFetchAppはUser-Agentの設定をサポートしていません -
❌ Web App の
doGet/doPostではHeaders情報の取得がサポートされていません -
❌ FaaS コールドスタート問題
-
❌ 複数人同時に開発することはサポートしていません
しかし、Web Appではあまり影響がなく、せいぜい数秒待ってページに入るだけです。
以上は GAS 自体のサービスの長所短所ですが、パッケージングツールの Web に与える影響はあまり大きくありません。この方法を Slack 方式と比較して選ぶと、より速く、軽量で、引き継ぎやすいという利点があります。欠点はチームがこのツールの URL と使い方をよく知っている必要があり、また GAS のライブラリ機能が限られていること(例:組み込みの暗号化アルゴリズムライブラリがない)ため、基本的に純粋な中継プラットフォームしか作れません。例えば審査申請を送る場合も、GitHub Actions に送信リクエストを転送するだけになります。
また、Google Workspace の作業環境のチームのみ対象です; リソースとニーズを考慮し、Google Apps Script — Web App を使ってパッケージングツールプラットフォームを構築しました。
UI フレームワーク
私たちは直接BootstrapのCDNを使用します。自分でCSSスタイルを作るのは面倒なので、BootstrapをAIにどう組み合わせて使うか尋ねる方が正確で便利です。
実践してみよう
こちらでプラットフォーム全体の構成をオープンソース化しましたので、各チームは自分たちのニーズに合わせてこのバージョンをカスタマイズしてください。
オープンソースサンプルプロジェクト
GAS 上で直接プロジェクトを表示:

GitHub リポジトリのバックアップ:
ファイル構成


とてもシンプルなクラスベースのMVC風アーキテクチャを書きました。調整したい場合や機能がわからないときは、AIに尋ねれば正確な答えが得られます。
システム
-
appsscript.json: GAS システムのメタデータ設定ファイル
ポイントは「oauthScopes」変数で、このスクリプトが使用する外部権限を宣言します。 -
Entrypoint.gs: doGet( ) エントリーポイントを定義する
コントローラー
- Controller_iOS.gs: iOSパッケージツールページのコントローラーで、Viewに表示するためのデータ取得を担当します
ビュー
-
View_index.html: パッケージングツールの全体構造とホームページ
-
View_iOS.html: iOS パッケージングツールページの骨組み
-
View_iOS_Runs.html: iOS パッケージングツール — パッケージ履歴内容ページ
-
View_iOS_Form.html: iOS パッケージングツール — パッケージングフォームページ
-
View_iOS_Runners.html: iOS ビルドツール — セルフホステッドランナーのステータスページ
モデル(ライブラリ)
-
Credentials.gs: キー内容を定義
(⚠️️️ご注意️、GASでGCP IAMを使うのはかなり複雑なため、ここで直接キーを定義しています。そのため、このGASプロジェクトには機密情報が含まれているので、プロジェクトの閲覧や編集権限を安易に共有しないでください ) -
StubData.gs: Online Demo 用のスタブメソッドとデータ。
-
Settings.gs: 一部の共通設定と lib の初期化。
-
GitHub.gs: GitHub API 操作のラップ。
-
Slack.gs: Slack API 操作のラップ。
-
Firebase.gs: Firebase — App Distribution API 操作のラッパー。
自分のパッケージングプラットフォームを作成する
- Google Apps Script プロジェクト を作成し、名前を付ける

プロジェクト設定 → 「エディタで『appsscript.json』マニフェストファイルを表示する」にチェックを入れると、「appsscript.json」メタデータファイルが表示されます。

2.参照私のオープンソースプロジェクトファイル、すべてのファイルを例に従って作成し、内容をそのままコピーしてください。

馬鹿げているが、仕方がない。
StubData.gs はまず一緒にコピーしてください。初回デプロイのテストで使用できます。
もう一つの方法は、clasp (Google Apps Script CLI) を使ってデモプロジェクトを git clone し、その後コードをプッシュすることです。

コピーが完了すると、サンプルプロジェクトと全く同じになります。
- 初回デプロイ「ウェブアプリケーション」で結果を確認する



プロジェクト右上の「デプロイ」→「新しいデプロイを追加」→ タイプ「ウェブアプリケーション」:
実行権限:
-
私は「すべてあなたのアカウントでスクリプトを実行します。」
-
ウェブアプリの利用者は、現在ログインしている Google アカウントのユーザーとしてスクリプトを実行します。
誰がアクセスできますか:
-
私だけです
-
XXX 同じ組織内のすべてのユーザー
同じ組織かつログイン済みのGoogleアカウントユーザーのみアクセス可能。 -
ログイン済みの Google アカウントユーザーはすべてアクセス可能です。
-
全員が
Googleアカウントにログインする必要はなく、誰でも公開アクセス可能です。
内部ツールの場合:「アクセス可能なユーザー:XXX 同じ組織内のすべてのユーザー」+「実行者の身分:ウェブアプリにアクセスするユーザー」を選択してセキュリティ管理が可能です。
デプロイ完了後の「ウェブアプリケーション」URLが、あなたのWeb AppパッケージツールのURLになります。チームメンバーと共有して使えます。(URLは見栄えが悪いので、短縮URLサービスで加工しても良いです。デプロイ内容の更新ではURLは変わりません)
ユーザーが初めて使用する際は許可を同意する必要があります
初めて Web App のURLをクリックすると、最初に認証の同意が必要です。



-
Review Permission → この Web アプリを使用するアカウントの権限を選択してください
-
未検証の警告ウィンドウで、「詳細」をクリックして展開 → 「XXX」へ移動(安全ではありません)をクリック

- 「許可する」をクリックしてください
以降、スクリプトの権限が変更されない限り、再認証は不要です。
認証同意が完了すると、パッケージングツールのホームページに移動します:

Demo パッケージツールのデプロイ成功 🎉🎉🎉
注:「このアプリケーションは Google Apps Script のユーザーによって作成されました」というメッセージは自動で非表示にできません。
デプロイの更新
⚠️すべてのコード変更は、デプロイの更新後に反映されます。
️️⚠️すべてのコードの変更は、デプロイの更新後に有効になります。
⚠️すべてのコード変更は、デプロイを更新しないと反映されません。
ここで注意すべきは、コードの変更を保存しても Web App に直接反映されないため、リロードしても変化がない場合はこのためです;「デプロイ」→「デプロイの管理」→「編集」→ バージョン「新しいバージョンを作成」→ 「デプロイ」→ 「完了」 の操作が必要です。


デプロイ完了後にページをリロードすると変更が反映されます。
テストデプロイを追加して開発を便利にする


前述の通り、すべての変更を反映させるにはデプロイの更新が必要です。これは開発段階では非常に面倒なので、開発中は「テストデプロイ」を使って変更が正しいか素早く検証できます。
「デプロイ」→「テストデプロイ作業」→ テスト用「ウェブアプリケーション」URLを取得。

開発段階では、このURLを直接使用して保存できます。ファイルの変更を保存したら、この開発用URLに戻ってページをリロードすると成果が確認できます!
すべての開発が完了したら、前述の手順に従って更新とデプロイを行い、ユーザーにリリースします。
Demoサンプルプロジェクトを実際のデータに接続する方法
次に本題です。実際のデータ連携として、GitHub Actions Workflow は前回の記事で作成した CI/CD フローを参照しています。実際の Actions Workflow に合わせてパラメータを調整してください。
⚠️変更前にご注意ください
Google Apps Script プラットフォームは複数人や複数ウィンドウでの開発をうまくサポートしていません。私が経験した問題は、誤って2つの編集ウィンドウを開き、Aで編集した後にBで編集したため、変更がBの古いバージョンで上書きされてしまったことです。したがって、同時に編集するのは1人1ウィンドウのみを推奨します。
GitHub 連携
GitHub API トークンの設定:
GitHub -> アカウント -> 設定 -> Developer Settings -> Fine-grained personal access tokens または Personal access tokens (classic)。
Fine-grained personal access tokens を使用することをお勧めします(安全ですが、有効期限があります)。
Fine-grained personal access tokens に必要な権限は以下の通りです:

-
Repo: 操作するリポジトリを選択してください
-
権限:
Actions (読み取り/書き込み)、Administration (読み取り専用)
もし特定の個人アカウントに依存したくない場合は、クリーンなチーム用の GitHub アカウントを作成し、そのトークンを使用することをお勧めします。
GAS プロジェクト → Credentials.gs → githubToken 変数にトークンを入力してください。
GithubStub を GitHub に置き換える:
到 GAS プロジェクト → Settings.gs → 次のように設定:
const iOSGitHub = new GitHubStub(githubToken, iOSRepoPath);
変更して
const iOSGitHub = new GitHub(githubToken, iOSRepoPath);
ファイルを保存する。
テスト用「ウェブアプリケーション」URLをリロードして変更が正しいか確認する:

正しくデータが表示されているということは: GitHubが本物のデータに正常に接続できた 🎉🎉🎉
「Runner 状態」に切り替えて、Self-hosted Runner の状態が正常に取得できているか確認できます:

注:私のランナーは起動していないため、オフライン状態です。
Slack 連携
Slack通知を連携するために、まずリポジトリのGitHub Actionsに戻り、パッケージビルドActionのワークフロー通知用コンテナActionを新規作成します。
CD-Deploy-Form.yml:
# ワークフロー(Action)名
name: CD-Deploy-Form
# Actions ログのタイトル名
run-name: "[CD-Deploy-Form] ${{ github.ref }}"
# 同じ Concurrency Group 内で新しいジョブがあれば実行中のジョブをキャンセル
# 例:同じブランチのビルドジョブが重複トリガーされた場合、前のジョブをキャンセル
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# トリガーイベント
on:
# 手動フォームトリガー
workflow_dispatch:
# フォーム入力フィールド
inputs:
# アプリのバージョン番号
VERSION_NUMBER:
description: 'アプリのバージョン番号(例:1.0.0)。空欄の場合はXcodeプロジェクトから自動検出。'
required: false
type: string
# アプリのビルド番号
BUILD_NUMBER:
description: 'アプリのビルド番号(例:1)。空欄の場合はタイムスタンプを使用。'
required: false
type: string
# アプリのリリースノート
RELEASE_NOTE:
description: 'デプロイのリリースノート。'
required: false
type: string
# トリガーしたユーザーのSlackユーザーID
SLACK_USER_ID:
description: 'SlackのユーザーID。'
required: true
type: string
# トリガーしたユーザーのメールアドレス
AUTHOR:
description: 'トリガーしたユーザーのメールアドレス。'
required: true
type: string
# ジョブ定義
jobs:
# ビルド開始時にSlackへメッセージ送信
# ジョブID
start-message:
# GitHubホストランナーで実行、小規模利用
runs-on: ubuntu-latest
# 最大タイムアウト設定、異常時の無限待機防止
# 通常は5分以上かからない
timeout-minutes: 5
# ジョブステップ
steps:
- name: Slackに開始メッセージを投稿
id: slack
uses: slackapi/[email protected]
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 }}>"
# ジョブ出力、後続ジョブで使用
# ts = SlackメッセージID、後続通知で同スレッドに返信可能
outputs:
ts: ${{ steps.slack.outputs.ts }}
deploy:
# ジョブはデフォルト並列実行、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: Slackに成功メッセージを投稿
uses: slackapi/[email protected]
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: Slackに失敗メッセージを投稿
uses: slackapi/[email protected]
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: Slackにキャンセルメッセージを投稿
uses: slackapi/[email protected]
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 のトークン値を設定するのを忘れないでください。
GAS プロジェクトに戻り → Credentials.gs → slackBotToken 変数にトークンを入力します。
再び GAS プロジェクト → Settings.gs → 次のように変更してください:
const slack = new SlackStub(slackBotToken);
変更して
const slack = new Slack(slackBotToken);
ファイルを保存する。
既存の Slack Bot App がなく通知を送るのが面倒な場合は、ここでのすべての手順を無視し、GAS プロジェクト内の slack に関する使用部分を削除してください。
GitHub 連携 — パッケージフォーム
GAS プロジェクト → Controller_iOS.gs → View_iOS_Form.html の内容を調整:
仮の Asana タスク連携方法を削除:
<? 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 を使ってオープン中の PR リストを取得します。必要に応じて残してください。
送信後の処理 iOSSubmitForm() の内容:
実際の GitHub Actions Workflow ファイル名や workflow_dispatch の inputs パラメータに応じて調整してください:
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パラメータを削除してください。
テスト用の「ウェブアプリケーション」URLをリロードして変更が正しいか確認する:

パッケージングフォームには「Opened PR List」だけが残っています。
データを入力して「リクエスト送信」を押し、ビルドフォームをテストしてください:

送信が成功したということは問題ないことを意味し、パッケージ記録に戻るとタスクが開始されているのも確認できます 🎉 :

重複したビルド記録は進捗を更新可能です。
よくある送信エラー:
Required input ‘SLACK_USER_ID’ not provided: GitHub Actions のこの SLACK_USER_ID フィールドは必須ですが、渡されていません。Slack の設定に失敗しているか、現在のユーザーのメールアドレスに対応する 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 Actions CD-Deploy.ymlでパッケージング作業を行う際に、Job Run IDをリリースノートに渡しています(もちろんBuild Numberに渡すことも可能です):
ID="${{ github.run_id }}" // ジョブ実行ID
COMMIT_SHA="${{ github.sha }}"
BRANCH_NAME="${{ github.ref_name }}"
AUTHOR="${{ env.AUTHOR }}"
# リリースノートを組み立てる
RELEASE_NOTE="${{ env.RELEASE_NOTE }}
ID: ${ID}
Commit SHA: ${COMMIT_SHA}
Branch: ${BRANCH_NAME}
Author: ${AUTHOR}
"
# Fastlaneでビルド&デプロイのレーンを実行
bundle exec fastlane beta release_notes:"${RELEASE_NOTE}" version_number:"${VERSION_NUMBER}" build_number:"${BUILD_NUMBER}"
これで Firebase App Distribution のリリースノートに Job Run ID が表示されます。
GAS Web App パッケージングツールプラットフォームは GitHub API と連携して GitHub Actions の実行履歴を取得し、API から得た Job Run ID を使って Firebase App Distribution API でリリースノートを検索します。リリースノートに *ID: XXX* が含まれるバージョンを見つけることで、対応するパッケージング記録を特定できます。
データベースを使用せずに2つのツールプラットフォームの対応が可能。


プロジェクト設定の連携
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() を使って実行ユーザーの権限に応じて動的にトークンを取得するため、トークンの設定は不要です。
GAS プロジェクト → Settings.gs → に移動して、以下を設定してください:
const iOSFirebase = new FirebaseStub(iOSFirebaseProject);
変更する
const iOSFirebase = new Firebase(iOSFirebaseProject);
すぐに可能です。
テスト用「ウェブアプリケーション」URLを打包記録にリロード → 記録の一つを選んで「ダウンロードリンクを取得」をクリック:


Firebase App Distribution のリリースノートで対応する Job Run Id のビルドが見つかった場合、ダウンロード情報が直接表示され、ダウンロードをクリックすると直接ダウンロードページに移動します。
完了!🎉🎉🎉
成果

ここまでで、サンプルはすべて実際に使えるパッケージングツールに変更済みです。残りのカスタマイズ機能や、さらに多くのサードパーティAPI連携、追加のフォームはご自身で拡張可能です(ChatGPTと相談しながら)。
最後に、開発とテストが完了したら必ず前述の手順に従い — デプロイの更新を行わないと反映されません!
連携拡張
「中継ステーション」役割の精神を受け継ぎ、ここにいくつかの迅速な連携用チートシートを提供します:
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")
// 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("Jiraの課題取得に失敗しました。");
}
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 Sheets を代わりに使用できます:
class Saveable {
constructor(type) {
// https://docs.google.com/spreadsheets/d/Sheet-ID/edit
const spreadsheet = SpreadsheetApp.openById("Sheet-ID");
this.sheet = spreadsheet.getSheetByName("Data"); // シート名
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");
// 書き込み
saveable.write("birthday_zhgchgli", "0718");
// 読み込み
saveable.read("birthday_zhgchgli"); // -> 0718
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(`Slackメッセージの送信に失敗しました: ${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 の事例:
まとめ
ご覧いただき、ご参加いただき誠にありがとうございます。CI/CD 0から1シリーズの記事はここで一区切りとなります。皆様とチームのCI/CDワークフロー構築に実際に役立ち、効率と製品の安定性向上に繋がれば幸いです。実装に関するご質問があればぜひコメントでご相談ください。この4本の記事は約14日以上かけて執筆しました。もし良かったら私の Medium をフォローし、友人や同僚と共有してください。
ありがとうございます。
🍺 Buy me a beer on PayPal
本シリーズの記事は多くの時間と労力をかけて執筆しました。内容があなたやチームの作業効率や製品品質の向上に役立った場合は、ぜひコーヒーをご馳走してください。ご支援ありがとうございます!

シリーズ記事:
-
CI/CD 実践ガイド(二):GitHub Actions と self-hosted Runner の使い方と構築大全
-
CI/CD 実践ガイド(三):GitHub Actions を使ったアプリプロジェクトの CI と CD ワークフローの実装
-
CI/CD 実践ガイド(4):Google Apps Script Web App を使って GitHub Actions と連携し、無料で使いやすいパッケージングツールプラットフォームを構築する
*Post MediumからZMediumToMarkdownによって変換されました。





コメント