gas-fakes emulates the Google Apps Script (GAS) environment natively within a Node.js runtime. By translating standard GAS service calls into granular API requests, it provides a high-fidelity, local sandbox for debugging, automated testing, and execution without the constraints of the Google Cloud IDE.
Microsoft as an Apps Script backend
Apps Script: A ‘Lingua Franca’ for the Multi-Cloud Era introduces the concept of replacing Apps Script's regular Workspace backend with ksuite. You write Apps Script code as normal, but behind the scenes gas-fakes translates the code into ksuite API requests.
I’m now adding the Microsoft Graph (Msgraph) backend. This represents a strategic evolution for the project. This addition allows developers to apply the familiar Apps Script programming model directly to the Microsoft 365 ecosystem.
By acting as a “lingua franca” for workspace platforms, gas-fakes enables you to treat the underlying productivity suite as a pluggable component.
This allows for the maintenance of a single business logic codebase that can target both Google Workspace and Microsoft 365.
Configuring the Microsoft Graph Backend
As usual, handling auth is the trickiest part of all this. However, the gas-fakes cli handles initializing and authentication against Azure to allow access to Msgraph. Just provide a normal apps script manifest that contains all the scopes you want to use.
gas-fakes init and auth will handle setting up the necessary app registration (this is similar to the Google Service Account). Access delegation is a little like Google domain wide delegation (DWD).
gas-fakes automatically translates the google oauthScopes section in your manifest into their Azure equivalents.
Initializing the platforms you ever want to use
Simply list the backends you want to be able to use in the init phase. The default is of course just “google”.
For this example, we want Apps Script to be able to access all 3 supported backends from the same project. You need az (the azure cli ) and gcloud (the google cli) installed.
gas-fakes init -b "google,msgraph,ksuite"
This will ask a series of questions and create any required registrations and service accounts, and create variables in your selected .env file
Authing the platform your project want to use
The auth phase will read the information you provided in the init phase, and execute the selected auth process for the selected back end platforms.
gas-fakes auth -b "msgraph,google"
This will set any permissions and scopes on the service accounts and registrations.
Keyless auth
In both google and microsoft, the authentication processes are ‘keyless’ – meaning gas-fakes doesn’t store service account credentials locally.
With Google, you have the choice of Application Default Credential (–auth-type adc) or the default domain wide delegation (the default –-auth-type dwd).
Microsoft is a kind of hybrid. Caveat: there are many variations depending on whether you have a SPO license, and using a business or consumer license. There are additional complications around multi tenants and other weird things.
I don’t have any of those. So I have only been able at this time to test the consumer account track in the auth process. This means you may get an occassional consent screen popping up occassionally even after the auth stage as the consumer track does not fully support delegation in the way that google does.
Development Experience
When targeting the msgraph backend, gas-fakes maps familiar GAS-style service synchronous calls to the Microsoft Graph API. You can now target OneDrive and Excel data without bothering to learn the nuances of the Microsoft Graph SDK.
Because the environment emulates the global GAS objects, the same code to process a Google Sheet can be applied to an Excel workbook on OneDrive without modification.
At the time of writing , I’ve only implemented a subset of the msgraph methods for OneDrive and Excel. I’ll add more over time.
Example: recursively list the entire contents of multiple platforms
Here’s an example app showing how you can combine platforms using the same Apps Script code. Here we are recursively list all the files in Drive, OneDrive and Ksuite
import '@mcpher/gas-fakes'
// run explore on each platform
const dual = () => {
ScriptApp.__platform = 'msgraph'
const rootFolder0 = DriveApp.getRootFolder()
console.log('--- msgraph Recursive Explorer ---')
explore(rootFolder0)
ScriptApp.__platform = 'ksuite'
const rootFolder = DriveApp.getRootFolder()
console.log('--- KSuite Recursive Explorer ---')
explore(rootFolder)
ScriptApp.__platform = 'google'
const rootFolder2 = DriveApp.getRootFolder()
console.log('--- Google Workspace Recursive Explorer ---')
explore(rootFolder2)
}
const explore = (folder, depth = 0) => {
const indent = ' '.repeat(depth)
console.log(`${indent}FOLDER: ${folder.getName()} (ID: ${folder.getId()})`)
// Show files in this folder
const files = folder.getFiles()
while (files.hasNext()) {
const file = files.next()
console.log(`${indent} FILE: ${file.getName()} (ID: ${file.getId()})`)
}
// Drill into subfolders
const folders = folder.getFolders()
while (folders.hasNext()) {
explore(folders.next(), depth + 1)
}
}
dual ()
Example: copying folder contents between platforms
Here we use the same code to copy all the files in a given folder between various combinations of platforms, and validate that each file content has been successfully written
import '@mcpher/gas-fakes'
const demoTransfer = () => {
// copy the files from ksuite
copyFiles({
sourcePlatform: 'ksuite',
targetPlatform: 'google',
sourceFolderName: 'gas-fakes-assets',
targetFolderName: 'from-ksuite-to-google'
})
// and back again
copyFiles({
sourcePlatform: 'google',
targetPlatform: 'ksuite',
sourceFolderName: 'from-ksuite-to-google',
targetFolderName: 'from-google-to-ksuite'
})
// now copy them from google to ms-graph
copyFiles({
sourcePlatform: 'google',
targetPlatform: 'msgraph',
sourceFolderName: 'from-ksuite-to-google',
targetFolderName: 'from-google-to-ms-graph'
})
// and back again
copyFiles({
sourcePlatform: 'msgraph',
targetPlatform: 'ksuite',
sourceFolderName: 'from-google-to-ms-graph',
targetFolderName: 'from-ms-graph-to-ksuite'
})
// check that the final files in ksuite match the original
const sourceBlobs = getBlobs({
sourcePlatform: 'ksuite',
sourceFolderName: 'gas-fakes-assets'
})
const finalBlobs = getBlobs({
sourcePlatform: 'ksuite',
sourceFolderName: 'from-ms-graph-to-ksuite'
})
// check blobs by checking their digest
if (sourceBlobs.length !== finalBlobs.length) {
throw new Error(`expected ${sourceBlobs.length} blobs but got ${finalBlobs.length}`)
}
sourceBlobs.forEach((b, i) => {
const sourceDigest = Utilities.base64Encode(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, b.getBytes()))
const finalDigest = Utilities.base64Encode(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, finalBlobs[i].getBytes()))
if (sourceDigest !== finalDigest) {
console.log(`${b.getName()} blob mismatch with ${finalBlobs[i].getName()}`)
}
})
}
const getBlobs = ({ sourcePlatform, sourceFolderName }) => {
// set which platform to use
ScriptApp.__platform = sourcePlatform
const sourceFolders = DriveApp.getFoldersByName(sourceFolderName)
if (!sourceFolders.hasNext()) {
throw new Error(`Source folder ${sourceFolderName} not found`)
}
// get the files in that source folder
const files = sourceFolders.next().getFiles()
const blobsToCopy = []
while (files.hasNext()) {
const file = files.next()
blobsToCopy.push(file.getBlob())
}
return blobsToCopy
}
const copyFiles = ({ sourcePlatform, targetPlatform, sourceFolderName, targetFolderName }) => {
const blobsToCopy = getBlobs({ sourcePlatform, sourceFolderName })
// now use an alternative platform
ScriptApp.__platform = targetPlatform
// create the folder if it doesn't exist
const targetFolders = DriveApp.getFoldersByName(targetFolderName)
let targetFolder = targetFolders.hasNext()
? targetFolders.next()
: DriveApp.createFolder(targetFolderName)
// now copy the blobs to the target folder
blobsToCopy.forEach(blob => {
targetFolder.createFile(blob)
})
return blobsToCopy
}
demoTransfer()
Advanced Feature: Leveraging Native Apps Script Libraries
gas-fakes further bridges the platform gap by allowing the execution of native Apps Script libraries within the Node.js emulation layer. gas-fakes manages the loading and execution of external library dependencies. Any libraries mentioned in your Apps Script manifest will be loaded and available.
Example 3: Using a live apps script library across platforms
In this case, we’ll use the bmPreFiddler library to manipulate sheet contents in both platforms. Again we are leveraging gas-fakes sandbox to both clean up and limit access to intended files.
import '@mcpher/gas-fakes'
/**
* Creates a spreadsheet on the specified platform.
* @param {Object} params
* @param {string} params.platform - The target platform (e.g., 'google', 'msgraph').
* @param {string} params.title - The name of the new spreadsheet.
*/
const createSpreadsheet = ({ platform = "google", title }) => {
ScriptApp.__platform = platform
const ss = SpreadsheetApp.create(title)
console.log(`Created spreadsheet ${ss.getName()} on ${platform}`)
return ss
}
/**
* Sets the active platform for ScriptApp.
*/
const setPlatform = (name) => ScriptApp.__platform = name
/**
* Copies a sheet's data between two platforms and verifies the result.
* @param {Object} params
* @param {Object} params.source - Source details {platform, id, sheetName}.
* @param {Object} params.target - Target details {platform, title}.
*/
const copySheetBetweenPlatforms = ({ source, target }) => {
// Get a fiddler for the source
setPlatform(source.platform)
const fiddler = bmPreFiddler.PreFiddler().getFiddler(source)
// Create the output spreadsheet on the target platform
setPlatform(target.platform)
const dst = createSpreadsheet(target)
// Get a fiddler for the destination
const dstFiddler = bmPreFiddler.PreFiddler().getFiddler({
id: dst.getId(),
sheetName: source.sheetName,
createIfMissing: true
})
// Copy the data and dump to target
dstFiddler.setData(fiddler.getData()).dumpValues()
// Verify that both sheets match using fingerprints
const after = bmPreFiddler.PreFiddler().getFiddler({
id: dstFiddler.getSheet().getParent().getId(),
sheetName: dstFiddler.getSheet().getName()
})
if (after.fingerPrint === fiddler.fingerPrint) {
console.log('Bingo: Data matches perfectly')
} else {
console.log('Error: Data fingerprint mismatch')
}
}
// load any libraries
LibHandlerApp.load()
// enable sandbox mode
ScriptApp.__behavior.sandBoxMode = true;
// create some spreadsheets with data and copy between them
const source = {
id: "1h9IGIShgVBVUrUjjawk5MaCEQte_7t32XeEP1Z5jXKQ",
platform: "google",
sheetName: 'airport list'
}
// add that to sanbox for read without marking it for trashing
ScriptApp.__behavior.whitelistFile(source.id)
copySheetBetweenPlatforms({ source, target: {platform: 'msgraph', title: 'test-msgraph-libraries'}})
// cleanup any files created
ScriptApp.__behavior.trash()
Help us develop gas-fakes
gas-fakes is an open-source project. We encourage developers to collaborate, contribute to the extension of supported services, and help refine this bridge between the world’s most popular workspace platforms.
This would be especially helpful if you have Microsoft knowledge and would like to help develop the msgraph connection. Ping me on bruce@mcpher.com if you want to get involved.
Links
GitHub: gas-fakes
GitHub: gas-fakes-containers
More gas-fakes articles: desktop liberation
What is gas-fakes
gas-fakes is a powerful emulation layer that lets you run Apps Script projects on Node.js as if they were native. By translating GAS service calls into granular Google API requests, it provides a secure, high-speed sandbox for local debugging and automated testing.
Built for the modern stack, it features plug-and-play containerization—allowing you to package your scripts as portable microservices or isolated workers. Coupled with automated identity management, gas-fakes handles the heavy lifting of OAuth and credential cycling, enabling your scripts to act on behalf of users or service accounts without manual intervention. It’s the missing link for building robust, scalable Google Workspace automations and AI-driven workflows.
Watch the video
Top comments (0)