ZhgChg.Li

Google Apps Script Web App|GitHub Actions連携で無料CI/CD打包ツール構築|跨團隊共有を実現

開発チーム向けにGAS Web AppでGitHub、Slack、Firebase、Asana/Jira APIを連携し、中継プラットフォームを構築。打包作業を自動化し、効率的なCI/CD環境を無料で実現します。

Google Apps Script Web App|GitHub Actions連携で無料CI/CD打包ツール構築|跨團隊共有を実現
本記事は AI による翻訳です。お気づきの点があればお知らせください。

CI/CD 実践ガイド(4):Google Apps Script Web App を使って GitHub Actions と連携し、無料で使いやすいパッケージングツールプラットフォームを構築する

GAS Web App で GitHub、Slack、Firebase または Asana/Jira API と連携し、中継ステーションを構築して、チーム間で共有できるパッケージングツールプラットフォームを提供

Photo by Lee Campbell

Photo by Lee Campbell

はじめに

前回の「CI/CD 実践ガイド(三):GitHub Actions を使った App iOS CI と CD ワークフローの実装」では、App iOS プロジェクトの CI/CD 基盤機能を完成させ、自動テスト検証とパッケージング・デプロイができるようになりました。しかし、実際の製品開発プロセスでは、パッケージング・デプロイ作業は主に他の職能パートナーに引き渡して QA(品質保証)機能の検証を行うためのものです。この場合、CD のシナリオはエンジニアリング部門だけに限らず、QA、PM、デザイン(Design QA)、さらには経営者が先に試したい場合もあります。

GitHub Actions の workflow_dispatch 手動フォームトリガーは、簡単なフォームでユーザーがビルド操作を行えますが、対象が非エンジニアの場合は非常に使いにくいです。彼らは「ブランチとは何か?」「フィールドは入力すべきか?」「ビルドが完了したかどうかはどう確認するのか?」「完了したらどうやってダウンロードするのか?」などが分かりません。

また、権限管理の問題もあります。別の職能のメンバーに直接 GitHub Actions でビルドを使わせるには、そのメンバーのアカウントをリポジトリに追加する必要があり、セキュリティ管理上非常に危険かつ不合理です。ビルドフォームを操作するだけなのに、ソースコード全体を見せる必要があるのです。

Jenkinsは独立したWebツールプラットフォームがありますが、GitHub Actionsにはこの機能しかありません。

`workflow_dispatch のフォームスタイル`

workflow_dispatch のフォームスタイル

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

GAS Web App 中継サーバー

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 に転送。

操作要件: スマホとパソコンに対応。

権限要件: チーム組織のメンバーのみがアクセス可能に制限できること。

オンラインデモ 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(FaaS)方式で、サーバーの構築や運用管理が不要

  • ✅ 権限管理は 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 ビルドツール — Self-hosted Runner ステータスページ

Model(Lib)

  • Credentials.gs: キー内容の定義
    (⚠️️️ご注意ください️、GASでGCP IAMを使うのは非常に複雑なため、ここで直接キーを定義しています。そのため、このGASプロジェクトには機密情報が含まれているので、プロジェクトの閲覧・編集権限を安易に共有しないでください )

  • StubData.gs: オンラインデモ用のスタブメソッドとデータ。

  • Settings.gs: 一部のよく使う設定と lib の初期化。

  • GitHub.gs: GitHub API 操作のラッパー。

  • Slack.gs: Slack API 操作のラッパー。

  • Firebase.gs: Firebase — App Distribution API 操作のラッパー。

自分のパッケージングプラットフォームを作成する

  1. Google Apps Script プロジェクト を作成し、名前を付ける

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

  1. 私のオープンソースプロジェクトファイルを参考に、すべてのファイルをサンプル通りに作成し、内容をそのままコピーしてください。

馬鹿だけど、仕方がない。

まず StubData.gs をコピーしてください。初回デプロイのテストに使用できます。

もう一つの方法は、clasp (Google Apps Script CLI) を使ってデモプロジェクトをgit cloneし、その後コードをプッシュすることです。

コピーすると、サンプルプロジェクトと全く同じになります。

  1. 初回デプロイ「ウェブアプリケーション」で結果を確認する

プロジェクト右上の「デプロイ」→「新しいデプロイ」→ タイプ「ウェブアプリケーション」:

実行権限:

  • 統一はあなたのアカウントでスクリプトを実行します。

  • Webアプリにアクセスするユーザーは、現在ログインしているGoogleアカウントのユーザーとしてスクリプトを実行します。

誰がアクセスできますか:

  • 私だけです

  • XXX 同じ組織内のすべてのユーザー 同じ組織かつログイン済みのGoogleアカウントユーザーのみアクセス可能。

  • ログインしているすべての Google アカウントのユーザー ログインしている Google アカウントのユーザーは誰でもアクセス可能です。

  • 全員が Googleアカウントにログインする必要はなく、誰でも公開アクセス可能です。

内部ツールの場合:「誰がアクセス可能か:XXX 同じ組織内のすべてのユーザー」+「実行者の身分:ウェブアプリにアクセスしているユーザー」を選択してセキュリティ管理を行うことができます。

デプロイ完了後の「ウェブアプリケーション」URLが、あなたの Web App パッケージツールのURLになります。チームメンバーと共有して使えます。(URLは見た目が悪いので、短縮URLサービスで加工しても良いです。デプロイ内容を更新してもURLは変わりません

ユーザーが初めて使用する際に同意が必要

初めて Web App のURLをクリックすると、まず認証の許可が必要です。

  • Review Permission → この Web App を使用するアカウントの身分を選択してください

  • 未検証の警告ウィンドウで、「詳細設定」をクリックして展開 → 「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の古いバージョンで上書きされてしまったことです。したがって、同時に一人一つのウィンドウだけでスクリプトを編集することを推奨します

GitHub 連携

GitHub API トークンの入力:

GitHub -> アカウント -> 設定 -> 開発者設定 -> 詳細設定パーソナルアクセストークン または パーソナルアクセストークン(クラシック)。

Fine-grained personal access tokens の使用を推奨します(より安全ですが、有効期限があります)。

Fine-grained personal access tokens に必要な権限は以下の通りです:

  • Repo: 操作するリポジトリを選択してください

  • 権限: Actions (読み取り/書き込み)Administration (読み取り専用)

特定のアカウントに依存したくない場合は、クリーンなチーム用の GitHub アカウントを作成し、そのトークンを使用することをおすすめします。

GAS プロジェクト → Credentials.gsgithubToken 変数にトークンを入力してください。

GithubStub を GitHub に置き換える:

GAS プロジェクト → Settings.gs → 次のように変更してください:

const iOSGitHub = new GitHubStub(githubToken, iOSRepoPath);

変更後

const iOSGitHub = new GitHub(githubToken, iOSRepoPath);

ファイルを保存する。

テスト用「ウェブアプリ」URLをリロードして変更が正しいか確認してください:

データが正しく表示されている場合:GitHubが実際のデータへの接続に成功しました 🎉🎉🎉

「Runner 状態」に切り替えて、Self-hosted Runner の状態が正常に取得できているか確認できます:

注:私の 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: Post a Start Slack Message
        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: Post a Success Slack Message
        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: Post a Failure Slack Message
        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: Post a Cancelled Slack Message
        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.gsslackBotToken 変数にトークンを入力してください。

再到 GAS プロジェクト → Settings.gs → 次のように変更してください:

const slack = new SlackStub(slackBotToken);

変更後

const slack = new Slack(slackBotToken);

ファイルを保存する。

もし既存の Slack Bot App がなく通知を送るのが面倒な場合は、ここでのすべての手順を無視して、GAS プロジェクト内の slack に関する使用部分を削除してください。

GitHub 連携 — パッケージフォーム

GAS プロジェクト → Controller_iOS.gsView_iOS_Form.html の内容を調整:
ダミーの Asana タスク連携方法を削除:

      <? tasks.forEach(function(task) { ?>
      <option value="<?=task.githubBranch?>">[<?=task.id?>] <?=task.title?></option>
      <? }) ?>

ここでデフォルトブランチ(ここでは main )を自分で調整することもできます。

GAS プロジェクト → Controller_iOS.gsiOSLoadForm() の内容を調整:

  • template.tasks = Stubable.fetchStubAsanaTasks(); の Asana のスタブ接続メソッドを削除してください。
    Asana や Jira と連携したい場合は、直接 ChatGPT に連携方法を尋ねてください。

  • template.prs = iOSGitHub.fetchOpenPRs(); は実際に GitHub API を呼び出してオープン中の PR リストを取得します。必要に応じて残してください。

送信後の処理 iOSSubmitForm() の内容:

実際の GitHub Actions ワークフローファイル名や 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 のユーザーIDが見つからない可能性があります。

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をリリースノートに渡しています(もちろんビルド番号に渡すことも可能です):

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でのビルド&デプロイLaneの実行
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認証手続きが完了していません

アクセス権「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 のビルドが見つかった場合、ダウンロード情報が直接表示され、ダウンロードボタンをクリックすると直接ダウンロードページに移動します。

完了!🎉🎉🎉

成果

Demo Web App

Demo Web App

ここまでで、サンプルはすべて実際に使えるビルドツールに変更済みです。あとはカスタマイズ機能や、より多くのサードパーティAPI連携、追加のフォームなどを自由に拡張できます(ChatGPTと相談しながら)。

最後に、開発とテストが完了したら、必ず前述の手順に従って — デプロイの更新を行わないと反映されません!

連携の拡張

中継サーバーの役割を引き続き踏まえ、ここにいくつかの素早く連携するためのチートシートを提供します:

Asana API — タスクの取得:


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")

Jira API — チケット取得(JQL):

// 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 Sheet を代わりに使用できます:

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

Slack API とメッセージ送信方法:

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

本シリーズの記事は多くの時間と労力をかけて執筆しました。内容があなたやチームの業務効率や製品品質向上に役立った場合は、ぜひコーヒーをご馳走してください。ご支援ありがとうございます!

Buy me a coffee

🍺 Buy me a beer on PayPal

シリーズ記事:

Post MediumからZMediumToMarkdownにより変換。

GitHub で編集
この記事を改善
本記事は Medium で初公開
オリジナルを読む
この記事をシェア
リンクをコピー · SNS でシェア
ZhgChgLi
著者

ZhgChgLi

An iOS, web, and automation developer from Taiwan 🇹🇼 who also loves sharing, traveling, and writing.

コメント