GitHub Actionsで同一のGASコードを複数スクリプトIDに一括デプロイする


## はじめに
  GoogleスプレッドシートやGoogleドキュメントなどの複数のファイルに、同じGoogle Apps Script（GAS）コードを添付したいケースは少なくありません。しかし、これを手作業で行うのは時間がかかり、更新時の手間も大きくなります。

  そこで、GitHub Actionsとclaspを使用して、1つのGASコードを複数のスクリプトIDに対して一括でデプロイする方法を紹介します。

  ※今回の記事は以下の記事の派生となります。

https://qiita.com/venect_qiita/items/26634a32037e40a58508

## 背景
  弊社ではそれぞれの案件でGoogleスプレッドシートを使用しており、各テンプレートには固有の「スタンドアロン型GAS」を紐づけています。これらのGASコードはGitHubで一元管理しているものの、実際のデプロイに関して問題が生じています。
  
  具体的には、claspツールを使ってGASコードをデプロイする際、一つの.clasp.jsonファイルには**一つのスクリプトID**（デプロイ先）しか指定できないという制約があります。
  以下画像の「スタンドアロン型GAS」は問題ないものの、「案件ごとのスプシ」に紐づくGASに変更が生じた場合、案件の数だけデプロイ操作を繰り返す必要があります。
  バグ修正や機能追加のたびにこの作業を行うのは非効率です。また、手動操作によるミスや更新忘れが発生すると、案件間でのコードバージョンの不一致が生じる危険性もあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/de502db3-142f-d91c-1970-f9f2f21bb5eb.png)

## 概要

今回構築する仕組みは以下のような流れで動作します。

**必要なもの**
- GASコードを管理するGitHubリポジトリ
- 各GASのscriptId

**動作の流れ**
:one: config.jsonで複数のデプロイ先scriptIdを一元管理
:two: GitHub Actionsでmainブランチのマージをきっかけに自動デプロイ
:three: YAMLファイル内のシェルスクリプトで複数scriptIdに対して順次デプロイ

図にするとこんな感じです
![github-actions-clasp-workflow-simplified.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/d9981ba8-fa65-4079-b7a3-b7c9d66628bb.png)

## リポジトリの構成

最小限の要素のみ載せています。

```
gas-project/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── src/
│   ├── appsscript.json
│   └── code.ts
├── deploy.js
└── config.json
```

## 実装手順

### 1. config.jsonの作成

複数のスクリプトIDを管理するファイルを作成します。
形式は自由ですが`config.json`としました。
```json:config.json
{
    "scriptIds": [
        {
            "name": "Test1",
            "id": "#########################################################"
        },
        {
            "name": "Test2",
            "id": "#########################################################"
        },
        {
            "name": "Test3",
            "id": "#########################################################"
        }
    ],
    "rootDir": "./src"
}
```

### 2. GitHub Secretsの設定

GitHub Actionsでclaspを使用するには、GASへのアクセス認証情報が必要です。これらはGitHubのリポジトリ設定からSecretsとして登録します。

必要なSecrets：

- `CLASP_TOKEN`: claspの認証トークン

claspのトークンの取得方法：

1. ローカル環境で`clasp login`を実行
2. `~/.clasprc.json`ファイルの内容をコピー
3. GitHubのリポジトリ設定からSecretsとして登録

### 3. GitHub ActionsのワークフローYAMLファイル作成

`.github/workflows/deploy.yml`に以下のように記述します

```yaml:deploy.yml
name: Deploy GAS to Multiple Scripts

on:
  push:
    branches: [ main ]
    paths:
      - 'src/**'  # srcディレクトリの変更時のみ実行
  workflow_dispatch:  # 手動実行も可能

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16'

      - name: Install Dependencie
        run: npm install -g @google/clasp

      - name: Create clasp config file
        run: |
          echo '{"token":${{ secrets.CLASP_TOKEN }}}' > ~/.clasprc.json

      - name: Deploy to all script IDs
        run: node scripts/deploy.js
```

`deploy.js` に処理部分を書きます

```jsx:deploy.js
const fs = require('fs');
const { execSync } = require('child_process');

// 設定ファイル読み込み
const config = JSON.parse(fs.readFileSync('./config.json', 'utf-8'));
const scriptIds = config.scriptIds;
const rootDir = config.rootDir; // GASのソースディレクトリ

if (!Array.isArray(scriptIds) || scriptIds.length === 0) {
  console.error('No scriptIds found in config.json');
  process.exit(1);
}

scriptIds.forEach(({ name, id }, index) => {
  if (!name || !id) {
    console.warn(`Skipping entry ${index + 1} due to missing 'name' or 'id'`);
    return;
  }

  console.log(`\n Deploying to ${name}`);
  console.log(` Script ID: ${id}`);

  // clasp.json を src/ に作成
  const claspConfig = { 
    scriptId: id,
    rootDir: rootDir,
    fileExtension: "ts"
  };

  fs.writeFileSync('.clasp.json', JSON.stringify(claspConfig, null, 2));
  // clasp push 実行
  try {
    execSync(`clasp push -f`, { stdio: 'inherit' });
    console.log(`Successfully deployed to ${name}`);
  } catch (err) {
    console.error(`Failed to deploy to ${name}:`, err.message);
  }
});

```

### ポイント
自動化のため、`clasp push -f`で**強制的**にプッシュしています。

:::note warn
claspでは`clasp push`を行う際にappsscript.jsonに変更が加わっている場合、更新して良いかが求められます。
弊社の運用上は強制プッシュで問題ないと判断しましたが、もし強制プッシュしたくない場合は`-f`を消してください。
:::

また、`.ts`をプッシュする場合は`"fileExtension": "ts"`が必要になります。

#### デプロイの効率化

srcディレクトリに変更があった場合のみワークフローを実行するよう設定しています。これにより、READMEやその他のファイルの変更時に不要なデプロイが実行されるのを防ぎます。

#### 手動実行を追加

`workflow_dispatch`を追加することで、GitHub上から手動でデプロイを実行することも可能になっています。今回はsrcディレクトリの変更がトリガーになるため、ワークフローの定義を変えたときなどは手動で実行する必要があります。

手動デプロイはActionsページの`Run workflow`から実行します。

![スクリーンショット 2025-04-17 18.30.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/053f681a-843a-4601-9588-a745ca55bb21.png)

中身を見ると正しくpushされていることがわかります。
![スクリーンショット 2025-04-17 18.40.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/1dae7770-4fef-403e-b4f2-670953c63e57.png)


## まとめ

GitHub Actionsとclaspを組み合わせることで、scriptIdを貼り替えてpushする手間が省けました。YAMLファイルとscriptファイルを分けたため、管理も容易になったと思います。

この方法を使うことで、共通機能や標準化されたツールの配布・更新が効率化され、一貫性のある状態を保つことができます。組織内でGASを活用している方は、ぜひ試してみてください。