Google Apps Script開発をGithubとCIで楽にする

Google Apps Script(GAS)とは?

Google Apps Scriptは、Googleスプレッドシートで使うことができる、スクリプト言語です。Excelで言うところのVBAマクロと同じような位置付けとなります。

外部のサイトをスクレイピングしてシートに自動入力したり、複雑な計算処理を自動で行ったりすることができます。

何がすごいのか?

このGoogle Apps Scriptのすごい所は、書いたスクリプトをウェブ上から実行できる所にあります。例えば、Githubのリポジトリが更新されたら特定の処理を実行する、といったことが実現できます。しかも無料です!

ただし、スクリプトが確実に実行される保証がなく、大量のリクエストが発生するようなシチュエーションでは使うことができません。ちょっとしたツールを作る時に力を発揮します。

GithubでGASのソース管理がしたい!

さて、本題に入ります。この便利なGASですが、バージョン管理の機能は備わっているものの、ソースコードを管理する仕組みがありません。

幸いにもGoogleが公式に開発用パッケージを用意してくれているのでこれを利用します。

この記事の目標

  • ソースコードはGithubで管理する
  • ローカルから直接Google Apps Scriptにデプロイ禁止
  • Wercker(CI)を使ってリポジトリにPushされたら自動デプロイする

ターゲットとなるGASプロジェクトの準備

ターゲットとなるGoogle Apps Scriptのプロジェクトですが、スプレッドシートに紐づいたものと、GAS単体のものがあります。今回は、GAS単体のものを使用します。

まずはGoogle Apps Scriptを開きます。

Apps Script - 自分のプロジェクト

開いたら、新規スクリプトボタンを押します。

エディターが表示されるので、ファイル > 保存を選択して適当な名前で保存してください。今回はtestにしました。

最後に、作成したスクリプトのIDをメモしておきます。スクリプトIDは、URLに含まれています。

https://script.google.com/d/[この部分]/edit

既に作成済みのGASを対象にする場合も、このスクリプトIDを使用すればOKです。ただし注意点として、この記事の手順を踏むと最終的に既存のスクリプトはすべて削除されます。基本的には、新しいスクリプトを作成するのがおすすめです。

GASのデプロイに必要なclaspのインストール

claspはGoogle Apps Scriptをローカルからpull/pushするためのパッケージです。インストールはnpmで一発です。今回ローカルにインストールするclaspは、ログイン情報を取得することだけに使います。

npm install -g @google/clasp

インストールが完了したら、続いて以下のコマンドを実行します。

clasp login

このコマンドを実行するとwebブラウザが起動し、Googleアカウントへのアクセスを許可するかどうかを聞かれるため、許可してください。

claspをグローバルインストールした場合は~/.clasprc.jsonに認証情報が書き込まれます。今回はCIを使用するため、このファイルの中身が必要となります。

cat ~/.clasprc.json

上のコマンドを実行して中身をコピーしておいてください。ただし、このファイルは認証情報が含まれているため、絶対に公開しないでください。

Githubリポジトリの準備

管理したいGoogle Apps Scriptのコードと必要なファイルを準備していきます。

.claspignore

スクリプトをpushする際に除外するファイルを指定します。

**/**
!appsscript.json
!gas.js

この.claspignoreを作らないと、Google Apps Scriptにpushした時に対応していないファイルが含まれていた場合にコケます。

appsscript.json

GASの設定ファイルです。中身は現時点で以下のようにしておけばいいみたいです。

{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER"
}

gas.js(メイン)

Google Apps Scriptで公開したいコードを記述します。今回はテストなので以下のようにしておきました。

function doGet(e) {
  var response = ContentService.createTextOutput("Push by clasp from ci");
  response.setMimeType(ContentService.MimeType.TEXT);
  return response;
}

wercker.yml

werckerの設定ファイルです。以下のように記述しておきます。

box: node:alpine

build:
  steps:
    - script:
        name: init .clasprc.json .clasp.json
        code: |
          echo $CLASPRC_JSON > .clasprc.json
          echo $CLASP_JSON > .clasp.json
    - npm-install
    - script:
        name: login
        code: |
          npx clasp login
    - script:
        name: push
        code: |
          npx clasp push

package.json

werckerでclaspをnpm installできるようにpackage.jsonを準備しておきます。

{
  "name": "",
  "version": "0.0.0",
  "description": "",
  "dependencies": {
    "@google/clasp": "^1.5.3"
  },
  "devDependencies": {
    "@types/google-apps-script": "^0.0.27"
  }
}

Visual Studio Codeで編集しやすいように"@types/google-apps-script"を一応インストールするようにしていますが、必須ではありません。

  • .claspignore
  • appsscript.json
  • gas.js
  • wercker.yml
  • package.json

これらのファイルは必須要素となります。ファイルの準備ができたらこれらのファイルを適当なリポジトリにPushしておきます。

werckerの準備

werckerでGithubの先ほど作成したリポジトリを対象とした新しいプロジェクトを作成し、Environmentに前段階でコピーしておいた情報を登録しておきます。

keyvalue
CLASPRC_JSON.clasprc.jsonの中身
CLASP_JSON{"scriptId":"スクリプトのID"}

リポジトリに変更をプッシュ

この時点で全ての準備が整っているはずです。Github上のリポジトリが変更されると、werckerにwebhookが送信され、Google Apps Scriptにコードがデプロイされる仕組みができあがりました。

これらの方法の問題点と改善点

claspを使用する際に.clasprc.jsonが必要になりますが、今回の方法だとキーの有効期限が切れた場合に、ローカルでclasp loginして新しいキーを登録しなおさなければいけません。この部分をどうやって改善するかを考えている状態です。なにか名案がありましたら、コメントを頂けると嬉しいです。

また、繰り返しになりますが、この方法を使うとターゲットに指定したプロジェクトの既存のスクリプトはすべて削除されます。必ず、リポジトリにコードをコピーするか、新規で作成したスクリプトをターゲットに設定してください。