claspとGitHub ActionsでGoogle Workspaceアドオンを配信する

8appsチーム·

gitプッシュからそのままApps Scriptへ — claspをインストールし、.clasp.jsonを設定し、あとはGitHub Actionsに任せる、ステップバイステップのガイド。

Google Workspaceアドオンを開発しているなら、ランタイムが少し変わった場所にあることに気づくはずです。Apps ScriptはSSHできないサーバーであり、scpでファイルを送れないUIでもあります。すべての変更はGoogleのエディタかAPI経由で送る必要があります。あなたのリポジトリとその環境をつなぐ橋が clasp(Command Line Apps Script)です。これをGitHub Actionsに包んでしまえば、デプロイはただの git push になります。

この記事では、パイプライン全体をエンドツーエンドで歩きます:claspのインストール、リポジトリと特定のApps Scriptプロジェクトを結びつける設定、そして最後に main への各コミットを直接Apps Scriptへ送り込むGitHub Actionsワークフロー。


claspとは何か

clasp はGoogleが出している小さなNode CLIで、Apps Script APIと通信します。メンタルモデルはシンプルです:

  • あなたのApps Scriptプロジェクトは script.google.com/.../edit のようなURLにあり、scriptId で識別されます。
  • clasp push はローカルディレクトリ内の .gs.htmlappsscript.json をスクリプトプロジェクトにアップロードし、サーバー側のコピーを置き換えます。
  • clasp pull はその逆です — 誰かがApps ScriptのWebエディタでコードを編集し、その変更をgitに戻したいときに便利です。
  • clasp deploy はバージョン付きの名前付きデプロイメントを切り出します — アドオンストアやエンドユーザーが実際に動かすのはこれです。これを実行しないと、clasp push@HEAD バージョンを更新するだけで、開発者以外の誰にも見えません。

ツールはこれだけです。ビルドシステムも、コードへの意見もありません — ディスク上のフォルダとGoogleのクラウド上のスクリプトプロジェクトを同期するだけの仕組みです。


ステップ 1 — claspをインストールする

clasp はグローバルなNodeパッケージです。Node 20以上がインストールされていることを確認してから:

npm install -g @google/clasp
clasp --version

claspがあなたのアカウントと通信できるようにするには、Googleユーザーに対してApps Script APIを有効化する必要があります。script.google.com/home/usersettings にアクセスし、Google Apps Script APIオン にしてください。これはアカウントごとに一度だけのスイッチです — 見落としがちで、忘れると clasp は分かりにくいエラーで落ちます。

ログインしましょう:

clasp login

ブラウザウィンドウが開き、OAuthフローを案内します。完了すると、claspは認証情報を ~/.clasprc.json に保存します。CIをセットアップするときにこのファイルに戻ってきます。


ステップ 2 — scriptIdを取得する

すべてのApps Scriptプロジェクトには一意な scriptId があります — claspはこれによって、どのプロジェクトにプッシュすべきかを判断します。取得方法は2通りあります。

プロジェクトがまだ存在しない場合、コマンドラインから作成します:

mkdir my-addon && cd my-addon
clasp create --type standalone --title "My Add-on"

--type には standalonesheetsdocsslides, formswebapp を指定できます。アドオンに合うものを選んでください。claspはGoogle側でプロジェクトを作成し、新しい scriptId がすでに記入された .clasp.json を書き出します。

プロジェクトがすでに存在する場合(Apps Scriptエディタで作ったか、誰かに共有された場合)は、script.google.com で開いてURLを見てください:

https://script.google.com/d/1AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdEfGhIjKlMnOp/edit
                              └────────────────────── scriptId ──────────────────────┘

/d//edit の間にある長い英数字部分があなたの scriptId です。Apps Scriptエディタの プロジェクト設定 → ID からも確認できます。


ステップ 3 — .clasp.json を設定する

.clasp.json は、ローカルディレクトリを特定のApps Scriptプロジェクトにピン留めするファイルです。リポジトリのルートに置きます:

// .clasp.json
{
  "scriptId": "1AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdEfGhIjKlMnOp",
  "rootDir": "./src"
}

2つのフィールド、どちらも重要です:

  • scriptId — claspがプッシュする先のプロジェクト。ステップ2で取得したものを貼り付けます。
  • rootDir — claspがプロジェクトのソースとして扱うローカルフォルダ。このフォルダ内のすべてがアップロードされ、それ以外は無視されます。rootDir を省略すると、claspはカレントディレクトリを使います — つまり通常は node_modules.git までアップロードしようとしてしまいます。必ず明示的に設定してください。

たまに見かける任意フィールド:

  • scriptExtensionshtmlExtensions は、claspがどの拡張子をサーバーコードまたはHTMLとして扱うかをカスタマイズします。
  • filePushOrder は特定のファイルを他より先にプッシュするよう強制します — .gs ファイル間にロード順の依存があるときだけ関係します。

ほとんどのプロジェクトでは、上記の2フィールド版で十分です。


ステップ 4 — ファイルを配置する

claspは rootDir の中にファイルがどう入ったかは気にしません。フォルダに有効なApps Scriptファイルが入っていれば、それをプッシュします。最小構成はこんな感じです:

my-addon/
  .clasp.json
  .claspignore          // 任意 — プッシュ時にスキップするファイル
  src/
    appsscript.json     // マニフェスト:スコープ、ランタイム、アドオン設定
    Code.gs             // サーバーサイド関数
    Sidebar.html        // HtmlServiceで配信するHTML

ファイルタイプについて押さえておくべき3点:

  • .gs ファイルはApps Scriptのサーバーコードです。claspがエディタのネイティブ形式と透過的に変換します。
  • .html ファイルは HtmlService.createHtmlOutputFromFile() で配信されるテンプレートです。
  • appsscript.json はプロジェクトのマニフェストです。OAuthスコープ、ランタイムバージョン(V8)、タイムゾーン、そしてアドオンの場合はユーザーに見えるメニューエントリやトリガーを宣言します。これがないとclaspはプッシュを拒否します。

rootDir の中にアップロードしたくないファイル(ビルドスクリプト、READMEなど、.gs/.html/appsscript.json 以外のもの)がある場合は、.clasp.json の隣に .claspignore を置きます:

**/**
!appsscript.json
!**/*.gs
!**/*.html

このパターンの意味は「すべてを無視し、その後Apps Scriptが実際に理解するファイル種別だけ無視を解除する」です。

レイアウトが整ったら、ローカルでは2つのコマンドでデプロイできます:

clasp push -f
clasp deploy --description "first version"

-f は対話式の「マニフェストが変更されました」確認をスキップします — 常にオンにしておいて構いませんし、後のCIでは必須です。


ステップ 5 — CIでclaspを認証する

ローカルのデプロイがうまくいくのは、clasp login~/.clasprc.json に認証情報を残してくれるからです。GitHub Actionsのランナーにはそのファイルがなく、ブラウザフローも実行できないので、認証情報をシークレット経由で直接渡します。

ファイルの中身はおおよそこんな感じです:

{
  "token": {
    "access_token": "ya29....",
    "refresh_token": "1//0g....",
    "scope": "https://www.googleapis.com/auth/script.projects ...",
    "token_type": "Bearer",
    "expiry_date": 1736000000000
  },
  "oauth2ClientSettings": {
    "clientId": "xxxxxxxxxxxx.apps.googleusercontent.com",
    "clientSecret": "GOCSPX-xxxxxxxxxxxx",
    "redirectUri": "http://localhost"
  },
  "isLocalCreds": false
}

refresh_token が要です — 誰かが許可を取り消さない限り、claspは何ヶ月もこれを使って新しいアクセストークンを発行できます。

GitHubに保存する手順:

  1. ワークステーションで clasp login を実行(ステップ1で済んでいればスキップ)。
  2. cat ~/.clasprc.json を実行し、出力をすべてコピー。
  3. GitHub上のリポジトリで:Settings → Secrets and variables → Actions → New repository secret
  4. Name: CLASPRC_JSONValue: いまコピーしたJSON全部。

💡 ヒント: このトークンは個人アカウントではなく、Apps Scriptプロジェクトを所有する専用のGoogleアカウントから生成してください。誰かがチームを離れてもローテーションは不要です。


ステップ 6 — GitHub Actionsワークフロー

ここからが本番です。.github/workflows/deploy.yml にこれを置きます:

name: Deploy Apps Script

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install clasp
        run: npm i -g @google/clasp

      - name: Restore clasp credentials
        run: echo '${{ secrets.CLASPRC_JSON }}' > ~/.clasprc.json

      - name: Push to Apps Script
        run: clasp push -f

      - name: Create a versioned deployment
        run: |
          DESC="ci-${GITHUB_SHA::7}-$(date -u +%Y%m%d%H%M)"
          clasp deploy --description "$DESC"

各ステップが何をしているか:

  • on: push: branches: [main]main へのマージごとにデプロイがトリガーされます。workflow_dispatch はActionsタブに「Run workflow」ボタンを追加し、手動での再実行を可能にします。
  • environment: production — GitHub Environmentにオプトインします。必須のレビュアーを設定すれば、すべてのデプロイは人間の承認を待ってから進みます。実ユーザーがいるアドオンには強くおすすめです。
  • Restore clasp credentials — シークレットを ~/.clasprc.json に書き戻し、claspが期待する場所で見つけられるようにします。シークレットを他の場所で catecho しないでください。GitHubは ${{ secrets.* }} 経由で使ったときだけ自動的にマスクします。
  • clasp push -frootDir 内のすべてをApps Scriptプロジェクトにアップロードします。-f はCIでは必須で、これがないと「マニフェスト変更」プロンプトでランナーが固まります。
  • clasp deploy --description — 新しいバージョン付きデプロイメントを切り出します。説明にはショートSHAとタイムスタンプを含めるので、どのデプロイメントもピンポイントのコミットまで辿れます。多くの人がここを忘れます — これがないと clasp push@HEAD を更新するだけで、インストール済みのユーザーには永遠に届きません。

ワークフローをコミットしてプッシュし、緑のチェックを待つだけ。これ以降、デプロイはただの git merge です。


やらないこと(とその理由)

~/.clasprc.json をリポジトリにコミットしない。 中にあるリフレッシュトークンは事実上の長寿命クレデンシャルです。GitHub Secretsに置くべきもので、ソース管理に置くものではありません。

チームで1つのGoogleアカウントを共有しない。 CLASPRC_JSON シークレットは、Apps Scriptプロジェクトを所有する専用アカウントから生成します。個人アカウントは入れ替わっても、デプロイ用アカウントは変わりません。

このワークフローではテストを走らせない。 Apps ScriptのランタイムはNodeとは十分に異なるので、意味のあるユニットテストを書くのは難しいです。共有コードのNodeポートに対するロジックテストは別ワークフローで動かし、各デプロイ後の手動スモークテストに頼っています。


得られるもの

このセットアップ前のデプロイはこうでした:最新を取り込み、claspのトークンが切れていないことを祈り、clasp push を実行し、まともな説明付きで clasp deploy するのを忘れず、-f を付け忘れないことを祈る。5ステップ、5箇所のつまずきポイント。

いまは:main にマージし、緑のチェックを見届け、アドオンを開く。Apps Scriptはもう特別な存在ではありません — 他のすべてと同じgitワークフローの後ろにある、ただのデプロイ先です。

Workspaceアドオンを保守していて、いまだにラップトップからプッシュしているなら、移行は半日仕事です。一番難しいのは ~/.clasprc.json を見つけることです。


カスタムワークフローのアイデアがありますか?
あなたのビジネスプロセスにぴったり合うGoogle Workspaceアドオンをオーダーメイドで作ります。support@8apps.co までご連絡ください。