📁

GASで自身のプロジェクトファイルを取得したい

2024/02/07に公開

こんにちは、luthです

GASからプログラミングに入門し、Vue/React、Typescriptを勉強していったノンプログラマーですが、チーム内で利用する、社内ツール開発を4年ほどやってきました

チーム内で開発していく中で、以下課題がありました

  • Nodeを扱えるレベルの開発者がチームにいない
  • Githubは社内ルールにより利用できない(開発部門ではないため)
  • DriveAPI系(GASのDriveApp含む)はドメインレベルで一部規制あり
  • 上記から、スクリプトのバックアップ/更新履歴は手動コピー以外に残す手段なし

色々と模索する中で、上記課題には「GASプロジェクトから、自身/別のGASプロジェクトを取得させて、スプレッドシートでGit管理」とかいう荒業を見出しました…!(強引)

この中の「GASプロジェクトから、自身/別のGASプロジェクトを取得させる」ところは他に記事を見なかったので、まとめてみます

*スプシ管理のところの仕様は各人のスキルレベル・チーム内リテラシに依存するかと思いますので、本稿の対象外としました

GASプロジェクトから、自身/別のGASプロジェクトを取得するコード

scriptFiles.gs
/**
 * [型定義用定数のためjsDoc外で利用しない] 取得したプロジェクトファイルデータ群の型定義
 * @type {{ projectName: string, scriptId: string, files: { id: string, name: string, type: 'json'|'gs'|'html', source: string }[] }}
 */
const ProjectFileType_ = undefined;

/**
 * [権限追加用定数のため利用しない] GAS側に「DriveAppのOAuth権限が必要!」と伝えるためだけの定数定義。
 */
const _drive_ = DriveApp.getStorageUsed;

/**
 *
 * @param {string} scriptId スクリプトID。プロジェクト自身から取得するなら`ScriptApp.getScriptId()`等で取得
 * @param {string} accessToken アクセストークン。対象プロジェクトにアクセス権を持つアカウントから`ScriptApp.getOAuthToken()`で取得
 * @returns {ProjectFileType_ || {}} `ProjectFileType_.files[].source`が各ファイル内のソースコード
 */
function getScriptFiles(scriptId, accessToken) {
  const downloadUrl = `https://script.google.com/feeds/download/export?id=${scriptId}&format=json`;
  let projectName;
  try {
    const res = UrlFetchApp.fetch(downloadUrl, {
      method: 'get',
      headers: {
        authorization: 'Bearer ' + accessToken,
        muteHttpExceptions: true
      }
    });
    const responseCode = res.getResponseCode();
    const responseHeaders = res.getHeaders();
    if (responseCode !== 200 || responseHeaders['Content-Type'] !== 'application/json') throw Error(res.getContentText());
    json = res.getContentText();
    
    json = JSON.parse(json);
    json.files = json.files.map(file => {
      file.type = file.type === 'server_js' ? 'gs' : file.type;
      file.name = `${file.name}.${file.type}`;
      return file;
    });
    projectName = (
      responseHeaders['Content-Disposition']
        .match(/filename\*=UTF\-8''(.+)\.json/i)
      || [, null]
    )[1];
    projectName = decodeURI(projectName);
  } catch (e) {
    console.error(e.stack);
    return {};
  }

  return {
    projectName,
    scriptId,
    files: json.files
  }
}

スクリプト取得例:実行ファイル自身

getOwnScript.gs
function getOwnScript() {
  const scriptId = ScriptApp.getScriptId();
  const accessToken = ScriptApp.getOAuthToken();
  const { files } = getScriptFiles(scriptId, accessToken);
  if (!files) throw Error('スクリプトファイル取得失敗');
  files.forEach(({ name, source }) => {
    console.log({ name });
    console.log(source);
  });
}

スクリプト取得例:他ファイルを複数管理

getOtherScripts.gs
function getOtherScripts() {
  const scriptIds = [
    'abcde01234fghijkl56789',
    'abcde01234fghijkl56780',
    'abcde01234fghijkl56781',
  ]
  const accessToken = ScriptApp.getOAuthToken();
  scriptIds.forEach(scriptId => {
    const { projectName, files } = getScriptFiles(scriptId, accessToken);
    if (!files) throw Error('スクリプトファイル取得失敗');
    files.forEach(({ name, source }) => {
      console.log({ projectName, fileName: name });
      console.log(source);
    });
  });
}

活用方法

  • スプレッドシートなどに書き込み、バックアップや編集履歴として利用する
  • diff系ライブラリを参考にして、バージョン間差分を取得する
    https://github.com/cemerick/jsdifflib

  • scriptFiles.gsについて、file.typeが設計と異なっていたため修正

Discussion