Abstract
Discover how to seamlessly integrate Google Workspace with GitHub Actions using the gas-fakes library. This guide demonstrates running Google Apps Script locally and within CI/CD pipelines without deploying Web Apps. Automate workflows, secure credentials, and effortlessly interact with Google Drive and Sheets directly from your repository.
Introduction
Google Apps Script (GAS) is a powerful low-code platform that enables developers to integrate, automate, and extend Google Workspace with ease. Ref Typically, executing GAS requires the script to be hosted on Google's servers via the Script Editor. While tools like clasp allow for local development and synchronization, running scripts from outside the Google ecosystem—such as from a local environment or a different cloud provider—often involves complex setups relying heavily on the Apps Script API or Web Apps. Ref
A common approach to triggering GAS from GitHub Actions is using Web Apps. However, this method presents several hurdles. Developers must re-deploy the Web App every time the script is updated, and verifying the latest code logic directly within the repository can be cumbersome. This is where gas-fakes becomes a game-changer. Ref Operating as a robust emulation layer, gas-fakes allows GAS projects to run on Node.js as if they were native, enabling seamless execution across various environments.
By leveraging gas-fakes within GitHub Actions, you can manage, test, and update your scripts directly inside your repository without the need for constant re-deployment. Furthermore, by storing sensitive credentials in GitHub's "Secrets and variables," you can ensure a high level of security for your automation workflows. This synergy between GitHub Actions and GAS opens up infinite possibilities for CI/CD application development. In this article, I will introduce a streamlined, professional method for managing Google Workspace using GitHub Actions and gas-fakes.
Phase 1: Setup and Authentication
To establish this pipeline, we first need to extract the proper authorization credentials and configure our GitHub repository.
Step 1: Obtain Authorization Data
In order to authorize gas-fakes, you must first generate Application Default Credentials (ADC) on your local machine. The Google Cloud CLI (gcloud) is required for this step. Detailed installation instructions can be found in the gas-fakes official documentation.
If you prefer using npx, execute the following commands in your terminal:
npx @mcpher/gas-fakes init --auth-type adc
npx @mcpher/gas-fakes auth
Alternatively, if you want to install the package globally via npm, run:
npm install -g @mcpher/gas-fakes
gas-fakes init --auth-type adc
gas-fakes auth
Once the authorization is complete, retrieve your credentials file.
For Linux / macOS:
cat ~/.config/gcloud/application_default_credentials.json
For Windows:
type C:\Users\{user name}\AppData\Roaming\gcloud\application_default_credentials.json
Copy the output. Your authorization data will look similar to the following JSON structure:
{
"account": "",
"client_id": "{your client ID}",
"client_secret": "{your client secret}",
"quota_project_id": "{your project ID}",
"refresh_token": "{your refresh token}",
"type": "authorized_user",
"universe_domain": "googleapis.com"
}
Step 2: Create a New GitHub Repository
To test this integration, create a new repository on GitHub. For this guide, we will assume the repository name is sample.
Step 3: Configure GitHub Secrets
We must securely store the authorization data in GitHub. Navigate to your repository's "Settings" -> "Secrets and variables" -> "Actions". Click on "New repository secret" and configure the following:
Key: GCP_ADC_USER_JSON
Value: (Paste the JSON string you copied in Step 1)
{
"account": "",
"client_id": "{your client ID}",
"client_secret": "{your client secret}",
"quota_project_id": "{your project ID}",
"refresh_token": "{your refresh token}",
"type": "authorized_user",
"universe_domain": "googleapis.com"
}
Phase 2: Building Your First Workflow
Step 4: Define the GitHub Actions Workflow
Create a new YAML file named sample1.yml inside the .github/workflows/ directory of your repository.
name: Run GAS via gas-fakes (sample1)
on:
push:
branches:["*"]
pull_request:
branches: ["*"]
issues:
types:[opened, edited, closed]
workflow_dispatch:
jobs:
execute:
name: sample1
runs-on: ubuntu-latest
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Create ADC file
run: |
echo '${{ secrets.GCP_ADC_USER_JSON }}' > /tmp/adc.json
- name: Extract Project ID from ADC
run: |
echo "PROJECT_ID=$(jq -r '.quota_project_id' /tmp/adc.json)" >> $GITHUB_ENV
- name: Run Google Apps Script by gas-fakes
run: |
export GOOGLE_CLOUD_PROJECT=$PROJECT_ID
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/adc.json
npx @mcpher/gas-fakes -s "const rootFolder = DriveApp.getRootFolder(); const rootFolderName = rootFolder.getName(); console.log(rootFolderName);"
In this initial test, a simple Google Apps Script snippet (const rootFolder = DriveApp.getRootFolder();...) is passed directly as an inline string within the YAML file.
Step 5: Push to the Remote Repository
Commit and push your local repository to GitHub. This action will automatically trigger the GitHub Actions workflow. You can monitor the execution under the "Actions" tab. If successful, the step "Run Google Apps Script by gas-fakes" will output logs similar to this:
...using env file in /home/runner/work/github-action-for-gas-fakes/github-action-for-gas-fakes/.env
...gas-fakes version 2.2.7[Worker] ...authorized backends: google via ADC (###@gmail.com)[Worker] ...using scriptId: ### (source: random)
マイドライブ
...terminating worker thread
Seeing your Google Drive's root folder name in the logs confirms that gas-fakes successfully authenticated and executed the GAS code natively via GitHub Actions.
Phase 3: Advanced Practical Applications
To demonstrate the full potential of this architecture, let's explore two practical CI/CD use cases: recording execution logs to Google Sheets and uploading repository diffs to Google Drive.
When this phase is finished, the directory structure of this repository becomes as follows.
Case 1: Record Execution Logs to Google Sheets
In this scenario, whenever a push, pull request, issue event, or manual trigger occurs, the workflow stores detailed execution logs in a Google Spreadsheet named sample spreadsheet for gas-fakes sample. If the file doesn't exist, it is automatically created in the root folder of your Google Drive.
Add the following YAML file to .github/workflows/sample2.yml:
name: Run GAS via gas-fakes (sample2)
on:
push:
branches: ["*"]
pull_request:
branches: ["*"]
issues:
types: [opened, edited, closed]
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
execute:
name: sample2
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Create ADC file
run: |
echo '${{ secrets.GCP_ADC_USER_JSON }}' > /tmp/adc.json
- name: Extract Project ID from ADC
run: |
echo "PROJECT_ID=$(jq -r '.quota_project_id' /tmp/adc.json)" >> $GITHUB_ENV
- name: Run Google Apps Script by gas-fakes
run: |
export GOOGLE_CLOUD_PROJECT=$PROJECT_ID
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/adc.json
npx @mcpher/gas-fakes -f sources/sample2.js
env:
GH_EVENT_NAME: "${{ github.event_name }}"
GH_ACTOR: "${{ github.actor }}"
GH_SHA: "${{ github.sha }}"
GH_REF: "${{ github.ref_name }}"
GH_COMMIT_MSG: "${{ github.event.issue.title || github.event.head_commit.message || github.event.pull_request.title || 'Manual Run' }}"
GH_RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
GH_REPO: "${{ github.repository }}"
GH_RUN_NUMBER: "${{ github.run_number }}"
GH_ISSUE_NUMBER: "${{ github.event.issue.number || '' }}"
GH_ISSUE_ACTION: "${{ github.event.action || '' }}"
GH_ISSUE_URL: "${{ github.event.issue.html_url || '' }}"
Next, create a new directory named sources and add the corresponding Google Apps Script file as sources/sample2.js. This script reads the environment variables passed by GitHub Actions and appends them to the spreadsheet.
/**
* Log GitHub Actions execution details to Google Sheets
* Updated to include Issue details
*/
function logExecution() {
const data = {
timestamp: new Date().toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" }),
event: process.env.GH_EVENT_NAME,
action: process.env.GH_ISSUE_ACTION,
issueNumber: process.env.GH_ISSUE_NUMBER,
actor: process.env.GH_ACTOR,
repo: process.env.GH_REPO,
branch: process.env.GH_REF,
runNumber: process.env.GH_RUN_NUMBER,
message: process.env.GH_COMMIT_MSG,
sha: process.env.GH_SHA,
url: process.env.GH_RUN_URL,
issueUrl: process.env.GH_ISSUE_URL,
};
const values = [
[
data.timestamp,
data.event,
data.action,
data.issueNumber,
data.actor,
data.message,
data.repo,
data.branch,
data.runNumber,
data.sha,
data.url,
data.issueUrl,
],
];
const headers = [
"Timestamp",
"Event",
"Action",
"Issue #",
"User",
"Message/Title",
"Repository",
"Branch",
"Run #",
"Commit SHA",
"Workflow URL",
"Issue URL",
];
const spreadsheet_name = "sample spreadsheet for gas-fakes sample";
const sheetName = "log";
const files = DriveApp.getFilesByName(spreadsheet_name);
const ss = files.hasNext()
? SpreadsheetApp.openById(files.next().getId())
: SpreadsheetApp.create(spreadsheet_name);
let sheet = ss.getSheetByName(sheetName) || ss.insertSheet(sheetName);
if (sheet.getDataRange().getValues().length <= 1) {
sheet.getRange(1, 1, 2, headers.length).setValues([headers, ...values]);
} else {
sheet
.getRange(sheet.getLastRow() + 1, 1, values.length, values[0].length)
.setValues(values);
}
console.log(
`Log updated for ${data.repo} (Event: ${data.event}, Run #${data.runNumber})`,
);
}
logExecution();
Case 2: Sync Repository Diffs to Google Drive
This workflow dynamically generates a .patch file containing code differences (or issue content) based on the triggered event, saving the result directly into a Google Drive folder named GitHub Sync - Repo Diffs.
Create .github/workflows/sample3.yml:
name: Run GAS via gas-fakes (sample3)
on:
push:
branches: ["*"]
pull_request:
branches:["*"]
issues:
types: [opened, edited, closed]
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
sync-to-drive:
name: sample3
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js Environment
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Configure Google Cloud Credentials
run: |
echo '${{ secrets.GCP_ADC_USER_JSON }}' > /tmp/adc.json
- name: Extract Project ID from ADC
run: |
echo "PROJECT_ID=$(jq -r '.quota_project_id' /tmp/adc.json)" >> $GITHUB_ENV
- name: Run Google Apps Script by gas-fakes
run: |
if[ "${{ github.event_name }}" == "pull_request" ]; then
REPO_DIFF=$(git diff origin/${{ github.base_ref }}...HEAD | base64 -w 0)
elif[ "${{ github.event_name }}" == "push" ] &&[ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then
REPO_DIFF=$(git diff ${{ github.event.before }}..${{ github.sha }} | base64 -w 0)
elif [ "${{ github.event_name }}" == "issues" ]; then
ISSUE_CONTENT="Issue Event: ${{ github.event.action }}\nTitle: ${{ github.event.issue.title }}\n\n${{ github.event.issue.body }}"
REPO_DIFF=$(echo -e "$ISSUE_CONTENT" | base64 -w 0)
else
REPO_DIFF=$(echo "Manual trigger: No diff." | base64 -w 0)
fi
export REPO_DIFF
export GOOGLE_CLOUD_PROJECT=$PROJECT_ID
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/adc.json
npx @mcpher/gas-fakes -f sources/sample3.js
Create the corresponding script at sources/sample3.js:
/**
* Syncs the repository-wide diff to a text file in Google Drive.
*/
function syncRepoDiff() {
const base64Diff = process.env.REPO_DIFF;
if (!base64Diff) {
console.warn("No diff content found in environment variable REPO_DIFF.");
return;
}
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const eventName = process.env.GITHUB_EVENT_NAME || "manual";
const filename = `diff_${eventName}_${timestamp}.patch`;
const folderName = "GitHub Sync - Repo Diffs";
try {
const decodedBytes = Utilities.base64Decode(base64Diff);
const blob = Utilities.newBlob(decodedBytes, "text/plain", filename);
const folders = DriveApp.getFoldersByName(folderName);
const folder = folders.hasNext()
? folders.next()
: DriveApp.createFolder(folderName);
const file = folder.createFile(blob);
console.log(`Successfully synced repository diff.`);
console.log(`Saved as: ${filename}`);
console.log(`View File: ${file.getUrl()}`);
} catch (error) {
console.error(`Error during Drive sync: ${error.message}`);
process.exit(1);
}
}
syncRepoDiff();
Execution and Verification
When a "push" event is triggered on the repository, the configured workflows execute concurrently. The visual output below confirms that GitHub Actions handled the integration flawlessly.
Executing the sample2 workflow processes the event payload and successfully appends a structured log directly into our Google Spreadsheet, functioning perfectly as an automated audit trail.
Executing the sample3 workflow correctly calculates the Git diff and uploads it to the designated Google Drive folder as a standard .patch file. Below is an example of the resulting text file contents generated in Google Drive:
diff --git a/README.md b/README.md
index d63c7d8..19f6f9d 100644
--- a/README.md
+++ b/README.md
@@ -246,3 +246,4 @@ jobs:
sample
sample
+ sample text
These results clearly demonstrate that Google Workspace services can be seamlessly managed and extended utilizing Google Apps Script and gas-fakes within a GitHub Actions CI/CD environment.
Exploring Further Applications
The integration of Google Apps Script and GitHub Actions via gas-fakes is not limited to logging and file syncing. This foundation unlocks numerous advanced automation possibilities for enterprise environments:
Automated Unit Testing
Traditionally, testing Google Apps Script code is a manual and tedious process. By utilizing gas-fakes, you can integrate standard JavaScript testing frameworks like Jest or Mocha. GitHub Actions can automatically run these test suites against your GAS functions on every pull request, ensuring robust code quality and preventing regressions before deployment.
Dynamic Document Generation
You can trigger Google Workspace document creation based on repository events. For instance, when a new GitHub Release is published, a workflow can use Google Docs or Google Slides services via gas-fakes to automatically compile release notes, insert relevant code snippets, and generate presentation slides for stakeholders.
Workspace Infrastructure as Code (IaC)
By storing organizational configurations—such as user directory structures, group memberships, or shared drive permissions—as YAML or JSON files in your repository, you can manage Google Workspace like infrastructure. Upon merging changes to the main branch, a workflow can execute a Google Apps Script that interfaces with the Admin Directory API to automatically synchronize the real-world Workspace environment with your repository's state.
Discussion: Evaluating the CI/CD Architecture
Integrating gas-fakes into your deployment pipeline shifts the paradigm of Google Workspace development. However, to maximize its potential, developers must weigh several technical considerations.
gas-fakes vs. Traditional clasp Workflows
Traditionally, developers rely on Google's clasp tool to synchronize local code with the Apps Script server. While effective for deployment, clasp cannot natively execute code locally. Testing requires pushing to Google’s servers and executing manually or via triggers. In contrast, gas-fakes provides a robust Node.js emulation layer. This enables instant execution, seamless integration with standard testing frameworks, and automated triggers directly from GitHub Actions without maintaining intermediate Web Apps.
The Strategic Importance of CI/CD in Workspace Development
Implementing CI/CD for Google Workspace is a major leap in enterprise security and maintainability. By shifting execution into GitHub Actions, sensitive credentials are removed from hardcoded scripts and securely managed via GitHub Secrets. Furthermore, every automation execution is firmly tied to a specific Git commit and workflow run, ensuring an immutable, highly auditable trail of operations that is essential for modern development standards.
Summary
- Integrates Google Apps Script into modern GitHub CI/CD pipelines using the gas-fakes library.
- Enables local execution of GAS code without the need for constant Web App deployments.
- Securely manages authentication using Google Application Default Credentials (ADC) and GitHub Secrets.
- Demonstrates practical automation cases including automated logging and repository-to-Drive synchronization.
- Provides a foundation for advanced unit testing and Infrastructure as Code (IaC) within Google Workspace.





Top comments (1)
Thanks for the detailed guide! I followed the steps and understand that gas-fakes allows local execution of Google Apps Script via GitHub Actions using ADC credentials stored in GitHub Secrets. The workflows (sample1.yml, sample2.yml, sample3.yml) demonstrate running inline GAS, logging execution to Google Sheets, and syncing repository diffs to Drive. This setup enables secure, automated CI/CD for Google Workspace, supports unit testing, dynamic document generation, and Infrastructure as Code, all without constant Web App deployments. Overall, it’s a robust way to integrate GAS into modern GitHub pipelines.