野良GASは許しまへんで(claspを活用したGoogle Apps Script運用方法の一例)
「修正を依頼された自動更新を行っているGASを探そうとしたのですが、無題のプロジェクトばかりで見つからないです」
「いつ誰がどんな意図で作ったかも分からないGASのメンテナンスを依頼されました...」
Google WorkspaceユーザーにとってGoogle Apps Script(以降GAS)は業務自動化を簡単に実現できる強力なツールです。しかし、無秩序に使い続けると、上記のような問題がいつかは表面化します。
Google Apps Scriptで実装された処理は業務に深く根ざしているケースも多いため、簡単に廃止することもできません。
はじめに
こちらはe-dash Developers Advent Calendar 2025の12日目の記事です。
e-dashでDX推進を担当しているyashiです。
この記事では、弊チームなりのGASの運用方法をご紹介します。「何番煎じだよ」というツッコミはあるかもしれませんが、どこかの誰かのGAS運用の参考になれば幸いです。
GAS運用で解決したかった課題と解決策
タイトルの通り、野良GASが蔓延っている状況が最も解決したかった課題ですが、それ以外にも以下のような解決したい課題がありました。
- 車輪の再発明:
- BigQueryからデータを取得する処理、Slackに通知する処理、Hubspotのプロパティの値を更新する処理など、どのプロジェクトでも利用することが多い処理を再利用できていない。
- 生成AIを最大限活用したい:
- GASエディタでは(現状は)生成AIの恩恵を受けることができない。弊社はCursorが導入されているのに、その恩恵を受けることができていない。
- コード品質のばらつき:
- リンターやフォーマッターを統一して適用できていない。
- バージョン管理が困難:
- Git管理されておらず、バージョン管理ができていない。
- 型安全性の欠如:
- 型を活用できておらず、実行時エラーが発生しやすい。
これらの問題を解決するため、claspを導入した上で、テンプレートベースのGASプロジェクト作成の仕組みを構築しました。
仕組みの概要
この仕組みは、テンプレートとプロジェクト作成スクリプトの2つで構成されています。claspを活用することで、ローカル環境(Cursor)で開発し、TypeScriptで型安全なコードを書くことができます。
テンプレートの基本構成は howdy39/gas-clasp-starter を参考にさせていただいています。
- テンプレート
- 標準化されたGASプロジェクトのテンプレート
- TypeScript、ビルドツール、共通処理などが最初から設定済み
- プロジェクト作成スクリプト
- テンプレートから新しいプロジェクトを生成するシェルスクリプト
- プロジェクト名の自動設定や依存関係のインストールを自動化
テンプレートの構成
技術スタック
テンプレートは以下のような技術を活用しています。
- TypeScript: 型安全な開発を実現
- Rollup: モジュールバンドラー(ESM形式で出力)
- ESLint + Prettier: コード品質の統一
- Jest: テストフレームワーク
- clasp: Google公式のCLIツール(GASプロジェクトの管理)
ディレクトリ構成
template/
├── src/
│ ├── appsscript.json # GASのマニフェストファイル
│ ├── index.ts # メインエントリーポイント
│ ├── config.ts # 設定ファイル
│ ├── bigquery.ts # BigQuery連携機能
│ ├── slack.ts # Slack連携機能
│ ├── hoge.ts # 上記以外にも共通関数を必要に応じて追加
│ └── types/
│ └── global.d.ts # 型定義
├── package.json # npm設定とスクリプト
├── tsconfig.json # TypeScript設定
├── rollup.config.mjs # Rollup設定
├── .eslintrc.json # ESLint設定
├── .clasp.json # clasp設定(テンプレート)
└── README.md # プロジェクト説明
プロジェクトの設定
package.json
テンプレートのpackage.jsonには、GAS開発に必要なスクリプトがあらかじめ定義されています。
"scripts": {
"clean": "rimraf dist",
"lint": "eslint --fix src/**/*.ts",
"test": "jest --passWithNoTests",
"bundle": "rollup --no-treeshake -c rollup.config.mjs",
"build": "npm run lint && npm run test && npm run clean && npm run bundle && npm run copy-appsscript",
"push": "npm run build && clasp push",
"security-fix": "npm audit fix",
"copy-appsscript": "copyfiles -f src/appsscript.json dist",
"open": "clasp open-container",
"open-script": "clasp open-script"
}
主なスクリプトの役割は以下の通りです。
| スクリプト | 役割 |
|---|---|
npm run build |
リント → テスト → クリーン → バンドル → マニフェストコピーを順次実行 |
npm run push |
ビルド後、claspでGASプロジェクトにデプロイ |
npm run open |
GASがバインドされているスプレッドシート等をブラウザで開く |
npm run open-script |
GASエディタをブラウザで開く |
tsconfig.json
TypeScriptの設定では、ES2020をターゲットにしつつ、厳格な型チェックを有効にしています。
{
"compilerOptions": {
"target": "es2020",
"module": "es2020",
"lib": ["es2020"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
ビルドの仕組み
Rollupを使用してTypeScriptファイルをバンドルし、GASで実行可能な形式に変換しています。
// rollup.config.mjs
export default {
input: 'src/index.ts',
output: {
dir: 'dist',
format: 'esm',
},
plugins: [
cleanup({ comments: 'none', extensions: ['.ts'] }),
license({
banner: [
`Name: ${packageJson.name}`,
`Version: ${packageJson.version}`,
`Description: ${packageJson.description}`,
`Author: ${packageJson.author?.name} <${packageJson.author?.email}>`,
`@see ${packageJson.homepage}`
].join('\n'),
}),
typescript(),
],
context: 'this',
};
ビルドプロセスのポイントは以下の通りです。
-
エントリーポイント:
src/index.tsを起点に依存関係を解決 -
出力形式: ESM形式で
dist/ディレクトリに出力 -
コメント削除:
cleanupプラグインでコメントを削除し、ファイルサイズを軽量化 - ライセンスバナー: 出力ファイルの先頭にプロジェクト情報を挿入(誰のどのプロジェクトか追跡可能に)
ビルド結果のdist/index.jsの先頭には以下のようなバナーが挿入されます。
/**
* Name: Sample project
* Version: 1.0.0
* Description: Zenn投稿用にサンプルで作成したプロジェクトです
* Author: Taro Yamada <t.yamada@example.com>
* @see https://github.com/example/sample-gas-project
*/
これにより、GASエディタで開いた際にも「このコードは何のプロジェクトか」「どこで管理されているか」「誰が開発したか」がすぐに分かります。
共通処理のメリット
テンプレートには、他システム(BigQueryやSlackなど)との連携処理が共通モジュールとして用意されています。
BigQuery連携(bigquery.ts)
export function fetchQuery(query: string): FetchQueryResponse {
// OAuth2認証を使用してBigQueryにアクセス
// ページネーションに対応した結果取得
}
Slack連携(slack.ts)
export function sendMessageToSlack(message: string, channelId?: string): void {
// SlackAppライブラリを使用した通知送信
}
export function sendFileToSlack(file: GoogleAppsScript.Drive.File, message?: string, channelId?: string): void {
// ファイル添付付きの通知送信
}
これらの共通処理には以下のメリットがあります。
- 車輪の再発明を防止: 認証やエラーハンドリングなど、気を使って実装する部分を再実装する必要がない。
- 生成AI活用時の安心感: 「AIが書いたコードをそのまま使って大丈夫か?」という不安を解消。AIは信頼できる共通処理の関数を使う形になるため、核となる部分は人間がレビュー済みのコードで担保される。
- メンテナンス性の向上: 仕様変更があった場合、テンプレートの該当ファイルを修正するだけ。他プロジェクトへの反映はAIを活用する。(AIの得意分野!)
プロジェクト作成スクリプト
次に、テンプレートを利用して新しいプロジェクトを作成するためのシェルスクリプトを説明します。
スクリプトの中身
新しいGASプロジェクトを作成する際は、create_gas_project.shを使用します。
#!/bin/bash
# プロジェクト名を引数から取得
if [ $# -eq 0 ]; then
echo "使用方法: $0 <プロジェクト名> [--install]"
exit 1
fi
PROJECT_NAME=$1
TEMPLATE_DIR="template"
TARGET_DIR="$PROJECT_NAME"
# テンプレートをコピー
cp -r "$TEMPLATE_DIR" "$TARGET_DIR"
# package.jsonのプロジェクト名を更新
sed -i '' "s/\"name\": \"<Your project name>\"/\"name\": \"$PROJECT_NAME\"/" "$TARGET_DIR/package.json"
sed -i '' "s/<Your repository name>/$PROJECT_NAME/" "$TARGET_DIR/package.json"
# --installオプションがある場合は依存関係をインストール
if [ "$2" = "--install" ]; then
(cd "$TARGET_DIR" && npm install && npm run security-fix)
fi
スクリプトの処理内容は以下の通りです。
- テンプレートフォルダを指定した名前でコピー
-
package.jsonのプロジェクト名とhomepageを自動更新 -
--installオプション指定時は依存関係のインストールと脆弱性修正を自動実行
実行方法
# google_apps_scriptディレクトリに移動
cd apps/google_apps_script
# 新しいプロジェクトを作成(依存関係も自動インストール)
./create_gas_project.sh my_new_project --install
# 作成されたプロジェクトに移動
cd my_new_project
プロジェクト作成後の作業は以下の通りです。
-
package.jsonのauthor、descriptionを編集(nameとhomepageは自動設定済み) -
src/config.tsでSlackチャンネルIDなどの設定を変更 - GASプロジェクトを作成し、
.clasp.jsonにスクリプトIDを設定 - エディタ上でGASの開発
-
npm run pushでデプロイ
運用方法
新規のGASプロジェクトについては、上記の仕組みの活用を徹底することで、野良GASが新規に生まれないようにしています。
既存のGASプロジェクトについては、改修の依頼がきたタイミングや手が空いているタイミングで、上記の仕組みへの移行を進めています。
正直、varで変数宣言されている複雑怪奇なJavaScriptを読み解いてTypeScriptに書き換えていくような作業は昔なら辛い作業でしたが、今はAIの力を借りることでサクサク進めることができています。AIに感謝!!
今後やりたいこと
解決したい課題がもう一つあります。それは...
「今まで月次レポートが自動で作られていたんですが、xxさんが退職したら動かなくなりました。」
GASのトリガーは作成したユーザーの権限で実行されるため、そのユーザーが退職するとトリガーが動かなくなるという問題があります。
現状は、退職者が出た際にpackage.jsonのauthorを検索し、該当プロジェクトに移動してnpm run open-scriptでGASエディタを開き、トリガーを手動で再設定するという運用でカバーしています。しかし、かなりの量の手作業が発生するこの方法はあまり美しい解決策ではありません。
もっとシステマチックな解決策をご存知の方は、ぜひコメント欄で教えていただけると嬉しいです!
まとめ
claspとテンプレートベースの開発フローを導入することで、以下の課題を改善することができました。
| 課題 | 改善策 |
|---|---|
| 野良GASの蔓延 | Git管理 + ライセンスバナーで追跡可能に |
| 車輪の再発明 | 共通処理をテンプレートに集約 |
| 生成AIを活用できない | ローカル開発(Cursor)が可能に |
| コード品質のばらつき | ESLint + Prettierで統一 |
| バージョン管理が困難 | Git管理 + package.jsonでのバージョニング |
| 型安全性の欠如 | TypeScript + strict modeで型チェック |
「野良GASは許しまへんで」という気持ちで、これからも社内の秩序を守っていきたいと思います。
最後までお読みいただきありがとうございました。
参考
- howdy39/gas-clasp-starter - 本テンプレートの基本構成(clasp + Rollup + TypeScript)はこちらを参考にさせていただきました
採用情報
GASでの業務自動化をはじめ、BPRやシステム導入・運用、AI活用を通して社内DXを推進するチームで新しい仲間を募集しています!
Discussion