github-octocat

こんにちは、kyoです。

以前LambdaのソースコードをGitHubに同期するツールを開発した話をエントリーしましたが、今回はその姉妹版として、Google Apps Scriptのコード同期ツールを開発した話をエントリーいたします。

Google Apps Script(GAS)とは?

Google Apps Script(以降はGASと略す)はGoogleDriveの機能の一つで、基本的にはExcelのVBAのように、Google SpreadsheetやGoogle Docsの組み込みスクリプトとして利用できます。
Standaloneのスクリプトも作成でき、さらにスクリプト毎にendpointのURLがあるので、GET/POSTのAPIを簡単に作れるようになっています。

今流行りのserverlessで言われると、大体はapigateway+lambdaなどの構成ですが、実はGASが最初のserverlessサービスかもしれません。
僕も私生活ではGAS+Spreadsheet+Slackの無料でチャット管理できる家計簿を作って家族で利用しています。

GASコード管理の現状

GASはLambdaのように、Web上のIDEで開発を行います。しかし、そのソースコードに関してバージョン管理機能は一切ありません…(せめてLambdaのVersion機能ぐらいは欲しかった。。。)
自分は個人と会社両方でGASを書くことがありますが、やはり ソースコードを管理できないと心細い ので、まず現状どういう解決策があるのか調べることにしました。

  • node-google-apps-script: Google Developers Blogの記事で紹介されているツールです。node製で、DriveAPIを通じてGASのプロジェクトをローカルに保存、開発、デプロイできます。
  • gas-manager: こちらもnode製で、node-google-apps-scriptより前に出たものです。基本的に機能は同じく、DriveAPIを使ってGASをローカルにダウンロードし、開発してまたアップロード・デプロイするものです。
  • Google Plugin for Eclipse: こちらはちょっと違う感じのものです。Eclipseのプラグインで、インストールするとEclipseからGoogleDriveに接続でき、GASを開発できるようです。
  • GasGit: あまり知られてないものですが、実は結構機能が充実しています。GASのプロジェクトを自動に解析し、ソースコード、依存関係をまとめてGitHubのレポジトリを作成できます。ただ設定と操作が面倒な感じでした。

今回の要件

あらためて、必要な要件を整理してみました:

  1. MustはGASのソースコードをGitHub/GHEに同期できること
  2. 手軽さ。豊富な機能より、基本機能を満たせば良いので、あとは簡単に設定、利用できること
  3. SpreadSheetなどに紐付くGASも管理できること
  4. 関連ツールを増やしたくない。元々Web上のIDEですべて完結するものなので、できればソースコードの管理もIDE内で済ませたい

これらの要件と各種ツールのマトリクスは以下になります:

ツール GitHub同期 手軽さ 管理対象 WebIDEで完結
node-google-apps-script
gas-manager
Google Plugin for Eclipse
GasGit

結果として、どれも要件を100%満たすことはできませんでした。
node-google-apps-scriptまわりはGoogleも推奨しているのですが、一度IDE上で修正すると、Git管理から外れることになってしまいます。
全てソースコードをローカルで開発・管理すれば良いけれども、WebIDEの便利さはやはり捨てがたいところ…。

そこで以前開発したLambdaのGitHub同期用のChrome拡張に、GASプロジェクトにあるソースコードを持って来れば、同期周りはすぐできるのではと思い、その方針でChrome拡張の開発を始めました。

そして、できたものはこちらになります:

執筆時点ではリリースから1ヶ月ほど経っていまして、特に宣伝してはいないものの、ユーザが着実に増えています。

pic1

Google+のGASコミュニティに紹介されて、GasGitの作者からも好評されました。
また、GitHub上のコントリビューターにより、拡張の紹介説明がロシア、フランス語などにも対応しました。

機能

この拡張は自分の需要を満たすために作ったものなので、上記の4つの要件を中心に開発を進めました。
拡張をインストールすると、GASのIDE上でGit関連のメニューが追加表示され、これらのメニューからGitHubの同期操作が行えます。
もちろんメニューや他の追加コンポーネントはすべてGASと同じUIで、違和感はないと思います。

pic2

詳細の機能は以下の通りになります。

  • GitHub/GitHub Enterpriseの同期
    pic5
    Must機能、GitHub上のリポジトリにバインドし、Pull/Pushの双方向の操作ができる、ブランチも対応。
    ただ、制限としてファイルの削除は対応しません(GAS側でgitのリビジョンがないので削除という操作を判別できないため)。
    具体的には、Pull時は同じ名前のファイルや新規ファイルが追加・更新されますが、GitHub上で削除したファイルはGAS上で削除しません。
    その逆も同じく、GASで修正、追加したファイルはGitHubに反映できますが、削除したものは反映しません。
    また、同期するファイルも選択できます。

  • 組み込みスクリプトに対応★
    この機能拡張の目玉機能です。
    現状、GasGit以外のツールはSpreadsheetなどに属している組み込みスクリプトの同期ができません。何故なら、それらのツールが利用しているDriveAPIは組み込みスクリプトをサポートしていないからです。
    今回開発した機能拡張はDriveAPIを利用せず(なのでGoogleの認証もいらない)、GAS自身のGWT RPCを利用しているため組み込みも対応できます。

  • GitHubのRepository、Branch作成に対応
    pic6
    一度リポジトリを作成してからのバインドだと手間がかかるので、GAS上から直接リポジトリ、ブランチを作成できるようにしています。

  • GASのネイティブUI
    pic7
    GASのCSSを利用したため、メニュー、ポップアップ、メッセージなどのコンポーネントはすべてネイティブ機能のUIに準じています。

  • GitHub上の階層構造を対応
    GAS内のファイル名はスラッシュを入れられるので、その機能を利用して階層構造を対応しています。
    例えばsrc/app/index.htmlのファイル名があれば、リポジトリにsrc/appのディレクトリが作成され、その配下にindex.htmlのファイルを作成されます。

  • Pull/Push時の差分確認、Commitコメント
    pic8
    GitHub風のDiff画面を出して差分確認が可能です。Push時は差分確認画面でCommitコメントを入力可能です。

  • GitHub Enterpriseと二段階認証を対応
    pic9
    GitHubと連携するためGitHub上のアクセストークンを作成するので、ログインはGitHub/GitHub Enterprise両方サポート、ログイン時の二段階認証も対応しています。

構成・実装

実装の詳細はGitHubのソースを見ればわかると思いますが、こちらでは全体の構成を簡単に紹介します。

GWT RPC

まず今回はDriveAPIを使わず、RPCを利用しました。ただ、このRPCはGASのIDEとサーバー側を通信するためのもので、詳細仕様は公開していません。今回は色々試した結果から推測する仕様で実装しました。そのため100%の動作保証はできません(現状特に同期関連のissueや質問が来てない)。

RPCの中身をデベロッパーツールで見てみると…:

pic3

Payloadの中身を見てみると、|で区切られた複数のリクエストパラメータであることは簡単に理解できます。
このリクエストはIDE上でファイルを開く時に送信したものです。そのpayloadの中にgetFileContentがあるということは、このリクエストはまさに表示しようとしたファイルを取得しているということです。
では、そのレスポンスも確認してみましょう:

pic4

Rawでみると以下のような文字列になります:

//OK[2,13,0,0,12,11,10,9,8,0,3,7,'VfBexSD',6,5,4,0,3,0,2,1,["d","e","1d","aaa\n","f","1e","","test","j","0a9112ba-4e7a-45ee-a0dd-90766d5c8711","M6iq1ys7wie_NNlawgo8SGlMNE-Uq10WR","k","g"],1,7]

「JSONじゃないのかよ!」とツッコミを入れたくなるところですが、APIではなくてRPCという時点で何となく察していたので、ここでは置いておくことにします。
頭部分のダブルスラッシュとステータス(OK/ERRORなどがある)を除くと、残りは普通にJSONでParse可能なArrayになりますので、処理はそこまで大変ではありません。

今回表示したファイルはtestという名前のhtmlファイルで、中身はaaaの三文字しか入っていません。レスポンスの中にtest"aaa\n"があるので、このレスポンスはファイルの中身を返していることがわかりました。

詳細は省きますが、後ろのuuidのようなものはファイルのidで、さらに後ろの長い文字列はプロジェクトのID、これはリクエストのpayloadにも入っているものです。
他にたくさんある奇妙な数字や一文字のパラメータは未だに解明していません、ただ、色々試した結果、今回の目的にはあまり影響がないので調査を終えました。

処理の流れ

このような感じで必要なRPCを一つ一つ解析し、機能拡張全体の処理フローが見えてきました:

  • まずはRPCコールする時に必要なセキュリティ周りのパラメータ(CSRFトークンなど)をChrome拡張のwebRequest権限を利用し、IDE自身が出したリクエストのヘッダーから抽出
  • IDE初期化のRPCコールを発行し、レスポンスからプロジェクトIDや全ファイルのIDなどの情報を取得
  • GitHubからバインドされたリポジトリの情報を取得
  • Push/Pull時はGWT RPCとGitHubのAPIで両方のソースコードを取得し、diff作成
  • Pushの場合はGitHubのAPIでソースコードをPush
  • Pullの場合は更新のRPCコールでファイルの中身を書き換え

あとはUI周りをうまくIDEにはめ込み、オプション画面でログイン機能を提供して完成、という流れになります。

終わりに

GASのソースコード管理で頭を悩ませる人は結構いると思います。
現に複数のソリューションがある中で今回の機能拡張を作った一番の理由は、やはりWebIDE上で簡単にGASのコードを管理したいという自分のこだわりでした。

こちらのリリース後、GistやBitbucketもサポートして欲しいなどの要望が複数来ていて、反響の大きさを感じています。今後もこのツールをさらに使いやすいように進化させていきたいと思います。