<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bruce Mcpherson</title>
    <description>The latest articles on DEV Community by Bruce Mcpherson (@brucemcpherson).</description>
    <link>https://dev.to/brucemcpherson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1550481%2Fc2487f13-aa4a-4dc6-a892-f250b1ab3b0f.jpg</url>
      <title>DEV Community: Bruce Mcpherson</title>
      <link>https://dev.to/brucemcpherson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brucemcpherson"/>
    <language>en</language>
    <item>
      <title>Combining local and hosted llm to minimize token cost</title>
      <dc:creator>Bruce Mcpherson</dc:creator>
      <pubDate>Tue, 09 Jun 2026 13:13:55 +0000</pubDate>
      <link>https://dev.to/brucemcpherson/combining-local-and-hosted-llm-to-minimize-token-cost-o69</link>
      <guid>https://dev.to/brucemcpherson/combining-local-and-hosted-llm-to-minimize-token-cost-o69</guid>
      <description>&lt;p&gt;My current large project is &lt;a href="https://github.com/brucemcpherson/gas-fakes" rel="noopener noreferrer"&gt;gas-fakes&lt;/a&gt;, which is an emulation that allows local execution, continuous integration, and containerization of native Apps Script code. In other words, we are not just ’emulating Apps Script’ – we are liberating it.&lt;/p&gt;

&lt;p&gt;Initially, AI generated code and testing was not something I was comfortable publishing, so to this point real people have coded and tested the majority of the repo. However, now the architecture and techniques are fully mature the remaining work is largely just busy work implementing and testing the remaining, less used, Apps Script platform methods.&lt;/p&gt;

&lt;p&gt;As of gas-fakes v2.5.3 we are at 4399/6708 methods and 10,500 parity tests on the emulation against the live Apps Script platform. Now feel a little more confident about allowing AI to do some of coding work.&lt;/p&gt;

&lt;p&gt;As an open source developer, my work is voluntary and unpaid, and therefore have to balance the potential token cost at my own personal expense, versus the value of any time saving I might make.&lt;/p&gt;

&lt;p&gt;This article is about combining the planning capability of antigravity, with the a free local model (Gemma running under oMLX on a Mac) doing the grunt work. Like this my Gemini costs are minimal, and the local heavy work is free.&lt;/p&gt;

&lt;p&gt;Note: this article is specific to Mac/AntiGravity combination. You can use a similar technique for other combinations but I’m not covering them here. gas-fakes collaborators will have this set up already implemented in their repo fork (but need to tweak the .gemini/settings to point to their local path for the mcp tools) , and the local model oMLX delegation will be ignored if they don’t have it running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzi66tqsalvuyk7aypok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzi66tqsalvuyk7aypok.png" alt=" " width="799" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation of the repo content.
&lt;/h2&gt;

&lt;p&gt;Before we start this is the status of the repo according to a Gemini assessment, before I start to use this local model to help with the grunt work. Clearly my priority is to maintain (or improve) the current quality.&lt;/p&gt;

&lt;p&gt;You can get a detailed analysis &lt;a href="https://github.com/brucemcpherson/gas-fakes/blob/main/notes/ai_scorecard.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Executive Summary &amp;amp; Core Metrics
&lt;/h3&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Evaluation Dimension&lt;/th&gt;
&lt;th&gt;Grade&lt;/th&gt;
&lt;th&gt;Key Focus Area / Findings&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Architectural Design &amp;amp; Viability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exceptional synchronous design mimicking V8 GAS on top of Node’s async landscape.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parity Tracking &amp;amp; Completeness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data-driven tracking system mapping thousands of live Apps Script methods via&amp;nbsp;&lt;code&gt;/progress&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing, Quality Assurance &amp;amp; Fidelity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Massive test footprint (~10,000+ internal/cyclical validation passes) proving true 1:1 behavioral parity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Edge-Case &amp;amp; Platform Oddities Handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A-&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deeply transparent about platform limits, script execution quirks, and modern auth drift.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ecosystem &amp;amp; Modern Stack Readiness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Integrated Model Context Protocol (MCP) server,&amp;nbsp;&lt;code&gt;gf_agent&lt;/code&gt;&amp;nbsp;automation tool, and containerization.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h4 id="%F0%9F%92%8E-overall-project-score-94100-enterprise-grade--production-dev-tool"&gt;&lt;span id="Overall_Project_Score_94100_Enterprise_Grade_Production_Dev_Tool"&gt;💎 Overall Project Score:&amp;nbsp;&lt;strong&gt;94/100&lt;/strong&gt;&amp;nbsp;(Enterprise Grade / Production Dev Tool)&lt;/span&gt;&lt;/h4&gt;

&lt;h2&gt;
  
  
  Gemini Directive &amp;amp; Hybrid Planned Hierarchy
&lt;/h2&gt;

&lt;p&gt;We’ll be using a strict, hierarchical delegation model.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Roles
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strategic Planner (Gemini)&lt;/strong&gt;: The hosted, powerful LLM. Its role is high-level planning, context management, decision-making, and orchestration. It determines what needs to be done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focused Executor (Local Model)&lt;/strong&gt;: The local, specialized LLM. Its role is high-fidelity, resource-intensive execution of specific tasks. It determines how the task is completed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Delegation Mechanism (query_local_model)
&lt;/h3&gt;

&lt;p&gt;The Planner is equipped with a &lt;a href="https://github.com/brucemcpherson/gas-fakes/blob/main/tools/omlx_mcp_server.cjs" rel="noopener noreferrer"&gt;specific tool&lt;/a&gt;, query_local_model. When the Planner determines that a task requires local computation, it does not attempt to solve it itself. Instead, it generates a structured call to query_local_model, passing the necessary context and instructions to the local MCP Server.&lt;/p&gt;

&lt;p&gt;The local model executes the task and returns the result to the Strategic Planner, which then integrates it into the final response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational Constraints (The Golden Rule)
&lt;/h2&gt;

&lt;p&gt;To prevent unnecessary cloud API usage, we can give the Planner strict directives:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Planner is strictly forbidden from drafting implementation details, writing production code, or creating tests directly when the query_local_model tool is available. If the task falls within the scope of a specialized, local execution, the Planner must delegate the task to the local Executor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We specify this as part of the &lt;a href="https://github.com/brucemcpherson/gas-fakes/blob/main/.agents/skills/gas-fakes-dev/SKILL.md" rel="noopener noreferrer"&gt;skills training&lt;/a&gt; – snippet of the important rule below:&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation &amp;amp; Focused Execution (CRITICAL DELEGATION GATE)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT] ZERO-TOLERANCE DELEGATION GATE You are FORBIDDEN from using write_file or replace to implement logic, write tests, perform refactoring, diagnose/fix debug errors, or draft documentation yourself. MANDATORY SEQUENCE:&lt;br&gt;
Gather context (Research).&lt;br&gt;
CALL omlx/query_local_model with a comprehensive prompt containing specific constraints.&lt;br&gt;
Review and synthesize the output.&lt;br&gt;
Apply changes to files (e.g., in src/, test/, etc.).&lt;br&gt;
EXCEPTION: This mandate only applies if the local model is available and its use has not been explicitly forbidden by the user. If unavailable or forbidden, you may proceed with the tasks using your own weights, but you MUST document the reason in your update_topic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Cost and Token Savings
&lt;/h3&gt;

&lt;p&gt;Hosted LLMs operating under token-based pricing complex can quickly accumulate unaffordable costs. By offloading the heavy lifting to the local model, you reduce the number of hosted tokens you have to pay for.&lt;/p&gt;

&lt;h2&gt;
  
  
  oMLX Setup and Documentation
&lt;/h2&gt;

&lt;p&gt;Since I’m using oMLX to serve my local model, let’s look at how to set that up. If you are not using a Mac, there are other local model orchestrators you can use, but the initial setup of those up is outside the scope of this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview: What is oMLX?
&lt;/h3&gt;

&lt;p&gt;oMLX allows the Planner to offload specific, resource-intensive tasks to a local, specialized LLM (the Executor).&lt;/p&gt;

&lt;p&gt;Instead of relying solely on the hosted API for every request, oMLX acts as a middleware layer. The hosted model dynamically decides when a task is best suited for local execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup and Configuration
&lt;/h3&gt;

&lt;p&gt;The core of the oMLX system is the MCP Server (Model Communication Protocol Server), which acts as the local endpoint for the Focused Executor.&lt;/p&gt;

&lt;h3&gt;
  
  
  The oMLX MCP Server
&lt;/h3&gt;

&lt;p&gt;an mcp tool acts as a local server. This server listens for requests from the hosted LLM (Gemini) and routes them to the locally running model instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration Methods
&lt;/h3&gt;

&lt;p&gt;You control the server and the overall system behavior via these environment variables. the mcp tool uses these to know where to delegate tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validating that AntiGravity is using the local server
&lt;/h3&gt;

&lt;p&gt;An important step to verify everything is working.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask the agy cli – ‘are you able to use the local model’&lt;/li&gt;
&lt;li&gt;The mcp server will inform you when it is are using the local model – you’ll see messages like this – &lt;code&gt;omlx/query_local_model(Delegate documentation generation to local model)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check the oMlx dashboard (&lt;a href="http://127.0.0.1:8000/admin/dashboard)-" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/admin/dashboard)-&lt;/a&gt; notice the ‘generating’ comment against the gemma model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72ry9i5m5m41qquystvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72ry9i5m5m41qquystvr.png" alt=" " width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;See this to get started with gas-fakes.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/brucemcpherson/gas-fakes" rel="noopener noreferrer"&gt;gas-fakes&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gasfakes</category>
      <category>node</category>
      <category>appsscript</category>
    </item>
    <item>
      <title>Bringing Apps Script to the desktop – the why, where and how of gas-fakes</title>
      <dc:creator>Bruce Mcpherson</dc:creator>
      <pubDate>Wed, 03 Jun 2026 13:20:05 +0000</pubDate>
      <link>https://dev.to/brucemcpherson/bringing-apps-script-to-the-desktop-the-why-where-and-how-of-gas-fakes-3ccg</link>
      <guid>https://dev.to/brucemcpherson/bringing-apps-script-to-the-desktop-the-why-where-and-how-of-gas-fakes-3ccg</guid>
      <description>&lt;p&gt;A brief summary on approaching parity of Google Apps Script methods and classes, some of the 'extras' gas-fakes provides for platform variety, production, development and testing and glimpse of some of the techniques used to get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: The Challenge of the Scripting Layer
&lt;/h2&gt;

&lt;p&gt;Google Apps Script (GAS) is a powerful tool for extending Google Workspace, automating workflows, and building lightweight backend services. It excels at rapid prototyping and integration within the Google ecosystem. However the limitations of the GAS environment (its proprietary runtime, laborious deployment cycle, and lack of modern tooling and debugging) become significant bottlenecks.&lt;/p&gt;

&lt;p&gt;We love the power and integration of GAS, but we require the debuggability and flexibility of a modern Node.js environment.&lt;/p&gt;

&lt;p&gt;This is the problem that &lt;strong&gt;gas-fakes&lt;/strong&gt; is designed to address.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; is an architectural emulation layer designed to bring the entire GAS runtime experience (its APIs, its behaviors, and its constraints) into a robust, controllable Node.js environment. It allows developers to write GAS-like code while leveraging the full power of modern software engineering practices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefn2mr3gm86norgc0qdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefn2mr3gm86norgc0qdp.png" alt="gas-fakes what" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Path to Parity: Emulating the GAS Runtime
&lt;/h2&gt;

&lt;p&gt;The core challenge in building &lt;code&gt;gas-fakes&lt;/code&gt; is the fundamental mismatch between the GAS execution model and the Node.js event loop. GAS is inherently asynchronous, relying on a managed, proprietary execution environment. Node.js, is designed for high-throughput, asynchronous execution and non-blocking I/O.&lt;/p&gt;

&lt;p&gt;Our architectural approach to achieving parity focuses on accurate API simulation and execution model transformation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The API Contract: Discovery Documents and REST
&lt;/h3&gt;

&lt;p&gt;The foundation of GAS is its reliance on Google's ecosystem of REST APIs, exposed through its own managed runtime. We've mapped the exact schema, parameters, and expected responses of every GAS service (e.g., &lt;code&gt;SpreadsheetApp&lt;/code&gt;, &lt;code&gt;GmailApp&lt;/code&gt;). This allows &lt;code&gt;gas-fakes&lt;/code&gt; to treat the GAS environment as a highly structured, predictable API contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Execution Model: Synchronous Emulation via Worker Threads
&lt;/h3&gt;

&lt;p&gt;The most complex hurdle is the transformation of GAS's asynchronous API calls (which often feel synchronous to the developer) into a predictable, synchronous-feeling flow within Node.js.&lt;/p&gt;

&lt;p&gt;We achieve this by employing &lt;strong&gt;Worker Threads&lt;/strong&gt; and &lt;strong&gt;Atomics&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Worker Isolation:&lt;/strong&gt; When a GAS method is called (e.g., &lt;code&gt;SpreadsheetApp.getActiveSpreadsheet().getRange().getValue()&lt;/code&gt;), the request is routed to a dedicated Worker Thread. This isolates the simulated GAS execution from the main Node.js event loop, preventing blocking while maintaining the illusion of synchronous execution.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Asynchronous-to-Synchronous Bridge:&lt;/strong&gt; The Worker Thread executes the simulated API call. Instead of returning a standard Promise, we use &lt;code&gt;Atomics&lt;/code&gt; to manage shared memory state between the Worker and the main thread. This allows the main thread to effectively "wait" for the combined result from the Worker, mimicking the blocking behavior of the original GAS runtime, while allowing the worker to execute multiple asynchronous activities. &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Importance of Behavioral Oddities
&lt;/h3&gt;

&lt;p&gt;A perfect API contract is insufficient. GAS has numerous subtle, undocumented behaviors such as rate limiting quirks, specific error codes, divergences from the associated API behavior, ID transformations, and timing dependencies that need to be understood and emulated for true parity.&lt;/p&gt;

&lt;p&gt;A significant part of the &lt;code&gt;gas-fakes&lt;/code&gt; development process is the meticulous documentation and reproduction of these &lt;strong&gt;behavioral oddities&lt;/strong&gt;. We don't just emulate the &lt;em&gt;happy path&lt;/em&gt;; we emulate the &lt;em&gt;edge cases&lt;/em&gt; and the &lt;em&gt;developer experience&lt;/em&gt; of the live GAS environment, ensuring that code written in &lt;code&gt;gas-fakes&lt;/code&gt; behaves identically to how it would in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 Rigorous Testing Regime: &lt;code&gt;gas-fakes&lt;/code&gt; fidelity assurance
&lt;/h3&gt;

&lt;p&gt;To validate the claim of behavioral parity, &lt;code&gt;gas-fakes&lt;/code&gt; operates under a massive, dual-environment testing regime. We maintain a comprehensive suite of &lt;strong&gt;over 10,500 tests&lt;/strong&gt; (as of version 2.5.3) that are executed across two distinct environments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Local &lt;code&gt;gas-fakes&lt;/code&gt; Emulator:&lt;/strong&gt; This allows for rapid, isolated unit and integration testing of the simulated runtime, ensuring the internal logic and API contracts are sound.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Live Google Apps Script Environment:&lt;/strong&gt; Crucially, every test suite is also executed against the actual, live GAS runtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This dual-environment verification ensures that the emulation is not merely "close," but &lt;strong&gt;behaviorally identical&lt;/strong&gt; to production GAS, including the precise handling of complex error states, rate limiting, and obscure edge cases that only manifest in the live Google ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Transparency and Auditing: The Documentation Layer
&lt;/h2&gt;

&lt;p&gt;Beyond achieving functional parity, &lt;code&gt;gas-fakes&lt;/code&gt; is engineered with a core commitment to engineering transparency. We recognize that for enterprise adoption, developers require not just a working emulator, but a fully auditable and self-documenting environment.&lt;/p&gt;

&lt;p&gt;To address this, we have built a sophisticated documentation layer that provides unprecedented visibility into the emulation process.&lt;/p&gt;

&lt;h3&gt;
  
  
  📚 Embedded API Documentation
&lt;/h3&gt;

&lt;p&gt;The repository includes a locally accessible and searchable version of the official Google Apps Script documentation. This documentation is integrated directly into the development environment, allowing developers to reference precise API definitions, parameter types, and expected behaviors without needing to switch context or leave their local codebase. This eliminates the friction of external documentation lookups, accelerating the development cycle while maintaining technical accuracy.&lt;/p&gt;

&lt;h3&gt;
  
  
  📊 Fidelity Progress Summary
&lt;/h3&gt;

&lt;p&gt;To manage the monumental task of API parity, we provide an automated &lt;strong&gt;Fidelity Progress Summary&lt;/strong&gt;. This system offers a clear, high-level overview of the implementation status for every class and method across all supported services. Developers can instantly see whether a specific function is &lt;code&gt;Completed&lt;/code&gt;, &lt;code&gt;In Progress&lt;/code&gt;, or &lt;code&gt;Not Started&lt;/code&gt;, providing a transparent roadmap of the emulation effort and allowing them to gauge the maturity of the API they are using.&lt;/p&gt;

&lt;p&gt;As of version 2.5.3, 4399 of Apps Scripts total of 6708 are implemented. &lt;/p&gt;

&lt;h3&gt;
  
  
  🔍 Deep Implementation Links: The Audit Trail
&lt;/h3&gt;

&lt;p&gt;A unique feature of &lt;code&gt;gas-fakes&lt;/code&gt; is deep implementation transparency. The documentation includes direct, clickable links to the &lt;strong&gt;exact line of source code&lt;/strong&gt; where every single method is implemented within the emulator. This feature allows for instant verification and auditing of the emulation logic. If a developer questions the behavior of &lt;code&gt;SpreadsheetApp.getRange()&lt;/code&gt;, they can instantly trace the call through the documentation to the specific line of code in &lt;code&gt;gas-fakes&lt;/code&gt; that dictates its behavior, providing a high level of trust and debuggability.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Beyond Parity: The gas-fakes Advantage
&lt;/h2&gt;

&lt;p&gt;While achieving parity is a huge task, another value of &lt;code&gt;gas-fakes&lt;/code&gt; lies in the capabilities it enables. Capabilities that are fundamentally impossible or prohibitively difficult within the constraints of the live Google Apps Script environment. This includes the elimination of theat troublesome 6 minute execution time limit on Live Apps Script.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; transforms the Apps Script language into a modern application development platform by separating its syntax from the place it traditionally runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 GAS Syntax for Regular Node Apps: Simplified Workspace Access
&lt;/h3&gt;

&lt;p&gt;A transformative features of &lt;code&gt;gas-fakes&lt;/code&gt; is its ability to inject the familiar, high-level syntax of Google Apps Script directly into a standard Node.js runtime. &lt;/p&gt;

&lt;p&gt;Even if you have no intention of ever deploying to Google Apps Script, you can use the simple, intuitive GAS syntax (e.g., &lt;code&gt;SpreadsheetApp&lt;/code&gt;, &lt;code&gt;DriveApp&lt;/code&gt;) in your regular Node.js apps. This provides a much simpler interface for accessing Workspace resources compared to the complex, low-level parameters of the raw REST APIs. It reduces cognitive load, improves maintainability, and abstracts away the complexity of OAuth scopes and request formatting.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔑 Auth Provisioning &amp;amp; Token Reuse: A Unified Credential Manager
&lt;/h3&gt;

&lt;p&gt;Managing authentication tokens across multiple services is an operational burden. &lt;code&gt;gas-fakes auth&lt;/code&gt; acts as a powerful, centralized credential manager. It provisions OAuth tokens for multiple backends (Google, KSuite, MS Graph) scoped to your provided manifest which can then be easily retrieved and &lt;strong&gt;reused&lt;/strong&gt; by other parts of your Node application or even by separate CLI tools. This creates a streamlined, single source of truth for credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  ☁️ Containerization and Multi-Cloud Deployment
&lt;/h3&gt;

&lt;p&gt;The entire GAS runtime emulation can be packaged using &lt;strong&gt;Docker&lt;/strong&gt;. This allows developers to deploy and run GAS-like logic in modern, serverless, and scalable multi-cloud environments. By leveraging the container image, &lt;code&gt;gas-fakes&lt;/code&gt; is fully compatible with leading cloud platforms, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Google Cloud Run&lt;/strong&gt;, &lt;strong&gt;Azure Container Apps&lt;/strong&gt;, &lt;strong&gt;AWS Lambda&lt;/strong&gt;, &lt;strong&gt;IBM Cloud&lt;/strong&gt;, and &lt;strong&gt;Kubernetes&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This capability fundamentally breaks the vendor lock-in associated with proprietary GAS deployment, allowing the same business logic to be executed in any serverless or containerized architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 CLI Workflow and Initialization
&lt;/h3&gt;

&lt;p&gt;While the core functionality of &lt;code&gt;gas-fakes&lt;/code&gt; provides a local execution environment, the initial setup and integration with live cloud services are handled through a streamlined Command Line Interface (CLI). This workflow is designed to minimize friction, automate complex configuration tasks, and ensure that your local testing environment perfectly mirrors the permissions and dependencies of your production Apps Script project.&lt;/p&gt;

&lt;h4&gt;
  
  
  🛠️ Streamlined Project Setup (&lt;code&gt;gas-fakes init&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;gas-fakes init&lt;/code&gt; command serves as the foundational step for any new project. It automates the tedious process of environment configuration, allowing developers to focus immediately on coding. It automatically generates a local &lt;code&gt;.env&lt;/code&gt; file, which securely stores necessary configuration variables and prompts the user to select the target cloud backends (e.g., Google Workspace, KSuite, MS Graph).&lt;/p&gt;

&lt;h4&gt;
  
  
  🔑 Authentication and Token Management (&lt;code&gt;gas-fakes auth&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Connecting a local environment to live cloud services requires managing complex OAuth flows and token lifecycles. The &lt;code&gt;gas-fakes auth&lt;/code&gt; command abstracts this complexity, providing a robust mechanism for secure, persistent authentication across all supported backends. It handles initial authorization guiding the user through browser redirects and manages secure token storage and refresh logic.&lt;/p&gt;

&lt;h4&gt;
  
  
  🔬 Automatic Scope Discovery: Precision Permissions
&lt;/h4&gt;

&lt;p&gt;The CLI automatically reads the &lt;code&gt;appsscript.json&lt;/code&gt; manifest file from your project. By parsing this file, &lt;code&gt;gas-fakes&lt;/code&gt; automatically infers and registers the exact OAuth scopes necessary for local execution. This ensures that your local environment is provisioned with the precise permissions required by the live project, guaranteeing parity without requiring developers to manually track or update scope lists.  &lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; even supports existing published Apps Script libraries. If they are mentioned in your manifest, they can be accessed remotely from live Apps Script and executed locally. &lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Local Web Server and RPC Testing
&lt;/h3&gt;

&lt;p&gt;In live GAS, testing a Web App requires deployment and a live URL. In &lt;code&gt;gas-fakes&lt;/code&gt;, you can use its cli to instantiate the entire GAS environment locally, deploy your code to a simulated endpoint, and test complex &lt;code&gt;google.script.run&lt;/code&gt; interactions and HTML Service templating with full local debugging tools without the need for any actual deployments. This accelerates the development feedback loop, with code changes showing up live in your local environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Multi-Backend Architecture
&lt;/h3&gt;

&lt;p&gt;Live GAS is tightly coupled to Google services. &lt;code&gt;gas-fakes&lt;/code&gt; decouples the business logic from the data source. By simply switching a configuration property, your GAS-like code can run against &lt;strong&gt;Google Services&lt;/strong&gt;, &lt;strong&gt;KSuite&lt;/strong&gt;, or &lt;strong&gt;MS Graph&lt;/strong&gt;. This enables hybrid architectures and sovereign cloud, with increased parity for non-Google backends currently being prioritized.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 The Automation Layer: CLI, &lt;code&gt;gf_agent&lt;/code&gt;, and &lt;code&gt;togas&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;While the sandbox provides the secure environment, the &lt;strong&gt;&lt;code&gt;gas-fakes&lt;/code&gt; CLI&lt;/strong&gt; and the specialized &lt;strong&gt;&lt;code&gt;gf_agent&lt;/code&gt;&lt;/strong&gt; provide the intelligence and accessibility needed for modern automation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The CLI&lt;/strong&gt;: A robust command-line tool for running scripts, starting local servers, and managing the sandbox.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;gf_agent&lt;/code&gt;&lt;/strong&gt;: This AI-powered companion acts as a translator, bridging the gap between natural language intent and executable code. For example, a request like &lt;em&gt;"Summarize my last 5 emails and put them in a new spreadsheet"&lt;/em&gt; is instantly converted into optimized Apps Script and executed by your AI agent. This is a self learning skills agaent which can both enhance its own knowledge, and optionally contribute towards community skills.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;togas&lt;/code&gt; &amp;amp; Clasp Integration&lt;/strong&gt;: The &lt;code&gt;togas&lt;/code&gt; command acts as a high-level orchestrator for deployment. It automates the process of bundling and synchronizing local files with a live GAS project. It builds upon and enhances the core functionality of &lt;strong&gt;&lt;code&gt;clasp&lt;/code&gt;&lt;/strong&gt;, providing a streamlined local-to-cloud workflow and making the necessary adjustments to local 'Node specific' ES syntax.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Sandbox-Agent Synergy&lt;/strong&gt;: This combination enables safe, instant Workspace automation. The agent handles the &lt;em&gt;what&lt;/em&gt;, and the sandbox ensures the &lt;em&gt;how&lt;/em&gt;, guaranteeing that code only touches whitelisted resources without the overhead of building complex specialized agents or MCP servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🛠️ Modern Tooling and Developer Experience
&lt;/h3&gt;

&lt;p&gt;A big pain point for GAS developers is the lack of modern tooling. &lt;code&gt;gas-fakes&lt;/code&gt; eliminates this by providing a full Node.js development stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;NPM Ecosystem:&lt;/strong&gt; Utilize any modern NPM package, allowing access to thousands of specialized libraries.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Advanced Debugging:&lt;/strong&gt; Leverage industry-standard Node.js debuggers (e.g., VS Code, AntiGravity) to step through code, inspect variables, and trace execution paths—a luxury unavailable in the GAS runtime—while simultaneously inspecting client-side HTML Service code in the Chrome debugger in its original structure, line numberings and format.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🛡️ Granular Sandbox and Security
&lt;/h3&gt;

&lt;p&gt;The live GAS environment offers a broad permission model. &lt;code&gt;gas-fakes&lt;/code&gt; provides a fine-grained, developer-controlled sandbox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;File-Level Whitelisting:&lt;/strong&gt; Define exactly which files or modules the script is allowed to access.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Service-Level Permission Controls:&lt;/strong&gt; Explicitly define which simulated services (e.g., &lt;code&gt;GmailApp&lt;/code&gt;, &lt;code&gt;DriveApp&lt;/code&gt;) the script is permitted to call, allowing for highly secure, auditable execution environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This optional level of granularity gives protection against Vibe coding hallucination.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗 Hybrid Interoperability: Bridging Local and Live
&lt;/h3&gt;

&lt;p&gt;You often need to maintain state across environments. &lt;code&gt;gas-fakes&lt;/code&gt; supports &lt;strong&gt;Hybrid Interoperability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By integrating with external services like Redis or Upstash, &lt;code&gt;gas-fakes&lt;/code&gt; allows the local development environment to share cache data, properties, and session state with the live, deployed GAS instance. This means your local tests are not isolated; they are running against a realistic, persistent state, ensuring seamless transition from development to production. We provide a drop-in replacement property and cache service library for live Apps Script, so you can share exactly the same stores between your local environment and the live deployed GAS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Apps Script as a 'Lingua Franca'
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; elevates Apps Script, a powerful yet constrained scripting language, to the level of a modern, maintainable, and scalable application framework—a lingua franca for Google Workspace integration. It allows teams to write the code they know, test it with the fidelity they require, and deploy it with the control they deserve.&lt;/p&gt;

&lt;p&gt;We are not just emulating GAS; we are liberating it.&lt;/p&gt;

&lt;p&gt;Links&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ramblings.mcpher.com/the-why-and-how-of-gas-fakes/" rel="noopener noreferrer"&gt;This full article&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/brucemcpherson/gas-fakes/" rel="noopener noreferrer"&gt;This repo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>google</category>
      <category>javascript</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Run Apps Script on an Office 365 back end with gas-fakes</title>
      <dc:creator>Bruce Mcpherson</dc:creator>
      <pubDate>Mon, 16 Mar 2026 14:41:35 +0000</pubDate>
      <link>https://dev.to/brucemcpherson/run-apps-script-on-an-office-365-back-end-with-gas-fakes-1964</link>
      <guid>https://dev.to/brucemcpherson/run-apps-script-on-an-office-365-back-end-with-gas-fakes-1964</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/brucemcpherson/gas-fakes" rel="noopener noreferrer"&gt;gas-fakes&lt;/a&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft as an Apps Script backend
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ramblings.mcpher.com/apps-script-with-ksuite/" rel="noopener noreferrer"&gt;Apps Script: A ‘Lingua Franca’ for the Multi-Cloud Era&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;By acting as a “lingua franca” for workspace platforms, gas-fakes enables you to treat the underlying productivity suite as a pluggable component.&lt;/p&gt;

&lt;p&gt;This allows for the maintenance of a single business logic codebase that can target both Google Workspace and Microsoft 365.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the Microsoft Graph Backend
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes init&lt;/code&gt; and &lt;code&gt;auth&lt;/code&gt; 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).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; automatically translates the google oauthScopes section in your manifest into their Azure equivalents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initializing the platforms you ever want to use
&lt;/h3&gt;

&lt;p&gt;Simply list the backends you want to be able to use in the init phase. The default is of course just “google”.&lt;/p&gt;

&lt;p&gt;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. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes init -b "google,msgraph,ksuite"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will ask a series of questions and create any required registrations and service accounts, and create variables in your selected .env file&lt;/p&gt;

&lt;h3&gt;
  
  
  Authing the platform your project want to use
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes auth -b "msgraph,google"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will set any permissions and scopes on the service accounts and registrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyless auth
&lt;/h3&gt;

&lt;p&gt;In both google and microsoft, the authentication processes are ‘keyless’ – meaning gas-fakes doesn’t store service account credentials locally.&lt;/p&gt;

&lt;p&gt;With Google, you have the choice of Application Default Credential (&lt;code&gt;–auth-type adc&lt;/code&gt;) or the default domain wide delegation (the default &lt;code&gt;–-auth-type dwd&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Experience
&lt;/h2&gt;

&lt;p&gt;When targeting the msgraph backend, &lt;code&gt;gas-fakes&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: recursively list the entire contents of multiple platforms
&lt;/h3&gt;

&lt;p&gt;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&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mcpher/gas-fakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// run explore on each platform&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootFolder0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRootFolder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--- msgraph Recursive Explorer ---&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;explore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootFolder0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRootFolder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--- KSuite Recursive Explorer ---&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;explore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootFolder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootFolder2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRootFolder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--- Google Workspace Recursive Explorer ---&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;explore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootFolder2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;explore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;indent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;FOLDER: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; (ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Show files in this folder&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;indent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; FILE: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; (ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Drill into subfolders&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;folders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFolders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;explore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;dual &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example: copying folder contents between platforms
&lt;/h3&gt;

&lt;p&gt;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&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mcpher/gas-fakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;demoTransfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// copy the files from ksuite&lt;/span&gt;
  &lt;span class="nf"&gt;copyFiles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gas-fakes-assets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ksuite-to-google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// and back again&lt;/span&gt;
  &lt;span class="nf"&gt;copyFiles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ksuite-to-google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-google-to-ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// now copy them from google to ms-graph&lt;/span&gt;
  &lt;span class="nf"&gt;copyFiles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ksuite-to-google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-google-to-ms-graph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// and back again&lt;/span&gt;
  &lt;span class="nf"&gt;copyFiles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-google-to-ms-graph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ms-graph-to-ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// check that the final files in ksuite match the original&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceBlobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBlobs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gas-fakes-assets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalBlobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBlobs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ms-graph-to-ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// check blobs by checking their digest&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;finalBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`expected &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sourceBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; blobs but got &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;finalBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;sourceBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceDigest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base64Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;computeDigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DigestAlgorithm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBytes&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalDigest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base64Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;computeDigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DigestAlgorithm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;finalBlobs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;getBytes&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDigest&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;finalDigest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; blob mismatch with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;finalBlobs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getBlobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourceFolderName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// set which platform to use&lt;/span&gt;
  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sourcePlatform&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceFolders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFoldersByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;sourceFolders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Source folder &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// get the files in that source folder&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sourceFolders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getFiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBlob&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copyFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetFolderName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBlobs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourceFolderName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// now use an alternative platform&lt;/span&gt;
  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetPlatform&lt;/span&gt;

  &lt;span class="c1"&gt;// create the folder if it doesn't exist&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetFolders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFoldersByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;targetFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetFolders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;targetFolders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// now copy the blobs to the target folder&lt;/span&gt;
  &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;targetFolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;demoTransfer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Feature: Leveraging Native Apps Script Libraries
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; further bridges the platform gap by allowing the execution of native Apps Script libraries within the Node.js emulation layer. &lt;code&gt;gas-fakes&lt;/code&gt; manages the loading and execution of external library dependencies. Any libraries mentioned in your Apps Script manifest will be loaded and available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Using a live apps script library across platforms
&lt;/h3&gt;

&lt;p&gt;In this case, we’ll use the &lt;a href="https://ramblings.mcpher.com/vuejs-apps-script-add-ons/helper-for-fiddler/" rel="noopener noreferrer"&gt;bmPreFiddler&lt;/a&gt; library to manipulate sheet contents in both platforms. Again we are leveraging &lt;code&gt;gas-fakes&lt;/code&gt; sandbox to both clean up and limit access to intended files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mcpher/gas-fakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * 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.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createSpreadsheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Created spreadsheet &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ss&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Sets the active platform for ScriptApp.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setPlatform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * 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}.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copySheetBetweenPlatforms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Get a fiddler for the source&lt;/span&gt;
  &lt;span class="nf"&gt;setPlatform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fiddler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bmPreFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PreFiddler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getFiddler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Create the output spreadsheet on the target platform&lt;/span&gt;
  &lt;span class="nf"&gt;setPlatform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSpreadsheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Get a fiddler for the destination&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dstFiddler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bmPreFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PreFiddler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getFiddler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;createIfMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Copy the data and dump to target&lt;/span&gt;
  &lt;span class="nx"&gt;dstFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;dumpValues&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify that both sheets match using fingerprints&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bmPreFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PreFiddler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getFiddler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dstFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSheet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getParent&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dstFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSheet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fingerPrint&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;fiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fingerPrint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bingo: Data matches perfectly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error: Data fingerprint mismatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// load any libraries&lt;/span&gt;
&lt;span class="nx"&gt;LibHandlerApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// enable sandbox mode&lt;/span&gt;
&lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__behavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sandBoxMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// create some spreadsheets with data and copy between them&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1h9IGIShgVBVUrUjjawk5MaCEQte_7t32XeEP1Z5jXKQ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;airport list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// add that to sanbox for read without marking it for trashing&lt;/span&gt;
&lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__behavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whitelistFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;copySheetBetweenPlatforms&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-msgraph-libraries&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;

&lt;span class="c1"&gt;// cleanup any files created&lt;/span&gt;
&lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__behavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Help us develop gas-fakes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;This would be especially helpful if you have Microsoft knowledge and would like to help develop the msgraph connection. Ping me on &lt;a href="mailto:bruce@mcpher.com"&gt;bruce@mcpher.com&lt;/a&gt; if you want to get involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/brucemcpherson/gas-fakes" rel="noopener noreferrer"&gt;gas-fakes&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/brucemcpherson/gas-fakes-containers" rel="noopener noreferrer"&gt;gas-fakes-containers&lt;/a&gt;&lt;br&gt;
More gas-fakes articles: &lt;a href="https://mcpher.com" rel="noopener noreferrer"&gt;desktop liberation&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What is &lt;code&gt;gas-fakes&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;
  
  
  Watch the video
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/oEjpIrkYpEM"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>appsscript</category>
      <category>workspace</category>
      <category>googlecloud</category>
      <category>office365</category>
    </item>
    <item>
      <title>Yes – you can execute native Apps Script with Office 365 back end</title>
      <dc:creator>Bruce Mcpherson</dc:creator>
      <pubDate>Mon, 16 Mar 2026 14:32:32 +0000</pubDate>
      <link>https://dev.to/brucemcpherson/yes-you-can-execute-native-apps-script-with-office-365-back-end-3pjo</link>
      <guid>https://dev.to/brucemcpherson/yes-you-can-execute-native-apps-script-with-office-365-back-end-3pjo</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/brucemcpherson/gas-fakes" rel="noopener noreferrer"&gt;gas-fakes&lt;/a&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft as an Apps Script backend
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ramblings.mcpher.com/apps-script-with-ksuite/" rel="noopener noreferrer"&gt;Apps Script: A ‘Lingua Franca’ for the Multi-Cloud Era&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;By acting as a “lingua franca” for workspace platforms, gas-fakes enables you to treat the underlying productivity suite as a pluggable component.&lt;/p&gt;

&lt;p&gt;This allows for the maintenance of a single business logic codebase that can target both Google Workspace and Microsoft 365.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the Microsoft Graph Backend
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes init&lt;/code&gt; and &lt;code&gt;auth&lt;/code&gt; 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).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; automatically translates the google oauthScopes section in your manifest into their Azure equivalents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initializing the platforms you ever want to use
&lt;/h3&gt;

&lt;p&gt;Simply list the backends you want to be able to use in the init phase. The default is of course just “google”.&lt;/p&gt;

&lt;p&gt;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. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes init -b "google,msgraph,ksuite"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will ask a series of questions and create any required registrations and service accounts, and create variables in your selected .env file&lt;/p&gt;

&lt;h3&gt;
  
  
  Authing the platform your project want to use
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gas-fakes auth -b "msgraph,google"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will set any permissions and scopes on the service accounts and registrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyless auth
&lt;/h3&gt;

&lt;p&gt;In both google and microsoft, the authentication processes are ‘keyless’ – meaning gas-fakes doesn’t store service account credentials locally.&lt;/p&gt;

&lt;p&gt;With Google, you have the choice of Application Default Credential (&lt;code&gt;–auth-type adc&lt;/code&gt;) or the default domain wide delegation (the default &lt;code&gt;–-auth-type dwd&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Experience
&lt;/h2&gt;

&lt;p&gt;When targeting the msgraph backend, &lt;code&gt;gas-fakes&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: recursively list the entire contents of multiple platforms
&lt;/h3&gt;

&lt;p&gt;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&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mcpher/gas-fakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// run explore on each platform&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootFolder0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRootFolder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--- msgraph Recursive Explorer ---&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;explore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootFolder0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRootFolder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--- KSuite Recursive Explorer ---&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;explore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootFolder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootFolder2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRootFolder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--- Google Workspace Recursive Explorer ---&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;explore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootFolder2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;explore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;indent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;FOLDER: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; (ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Show files in this folder&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;indent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; FILE: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; (ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Drill into subfolders&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;folders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFolders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;explore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;dual &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example: copying folder contents between platforms
&lt;/h3&gt;

&lt;p&gt;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&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mcpher/gas-fakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;demoTransfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// copy the files from ksuite&lt;/span&gt;
  &lt;span class="nf"&gt;copyFiles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gas-fakes-assets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ksuite-to-google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// and back again&lt;/span&gt;
  &lt;span class="nf"&gt;copyFiles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ksuite-to-google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-google-to-ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// now copy them from google to ms-graph&lt;/span&gt;
  &lt;span class="nf"&gt;copyFiles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ksuite-to-google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-google-to-ms-graph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// and back again&lt;/span&gt;
  &lt;span class="nf"&gt;copyFiles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-google-to-ms-graph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ms-graph-to-ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// check that the final files in ksuite match the original&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceBlobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBlobs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gas-fakes-assets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalBlobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBlobs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from-ms-graph-to-ksuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// check blobs by checking their digest&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;finalBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`expected &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sourceBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; blobs but got &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;finalBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;sourceBlobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceDigest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base64Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;computeDigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DigestAlgorithm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBytes&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalDigest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base64Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;computeDigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DigestAlgorithm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;finalBlobs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;getBytes&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDigest&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;finalDigest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; blob mismatch with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;finalBlobs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getBlobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourceFolderName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// set which platform to use&lt;/span&gt;
  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sourcePlatform&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceFolders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFoldersByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;sourceFolders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Source folder &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// get the files in that source folder&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sourceFolders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getFiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBlob&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copyFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetPlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourceFolderName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetFolderName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBlobs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sourcePlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourceFolderName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// now use an alternative platform&lt;/span&gt;
  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetPlatform&lt;/span&gt;

  &lt;span class="c1"&gt;// create the folder if it doesn't exist&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetFolders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFoldersByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;targetFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetFolders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;targetFolders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DriveApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetFolderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// now copy the blobs to the target folder&lt;/span&gt;
  &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;targetFolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blobsToCopy&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;demoTransfer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Feature: Leveraging Native Apps Script Libraries
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; further bridges the platform gap by allowing the execution of native Apps Script libraries within the Node.js emulation layer. &lt;code&gt;gas-fakes&lt;/code&gt; manages the loading and execution of external library dependencies. Any libraries mentioned in your Apps Script manifest will be loaded and available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Using a live apps script library across platforms
&lt;/h3&gt;

&lt;p&gt;In this case, we’ll use the &lt;a href="https://ramblings.mcpher.com/vuejs-apps-script-add-ons/helper-for-fiddler/" rel="noopener noreferrer"&gt;bmPreFiddler&lt;/a&gt; library to manipulate sheet contents in both platforms. Again we are leveraging &lt;code&gt;gas-fakes&lt;/code&gt; sandbox to both clean up and limit access to intended files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mcpher/gas-fakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * 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.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createSpreadsheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Created spreadsheet &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ss&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Sets the active platform for ScriptApp.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setPlatform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * 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}.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copySheetBetweenPlatforms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Get a fiddler for the source&lt;/span&gt;
  &lt;span class="nf"&gt;setPlatform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fiddler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bmPreFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PreFiddler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getFiddler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Create the output spreadsheet on the target platform&lt;/span&gt;
  &lt;span class="nf"&gt;setPlatform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSpreadsheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Get a fiddler for the destination&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dstFiddler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bmPreFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PreFiddler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getFiddler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;createIfMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Copy the data and dump to target&lt;/span&gt;
  &lt;span class="nx"&gt;dstFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;dumpValues&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify that both sheets match using fingerprints&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bmPreFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PreFiddler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getFiddler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dstFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSheet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getParent&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dstFiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSheet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fingerPrint&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;fiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fingerPrint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bingo: Data matches perfectly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error: Data fingerprint mismatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// load any libraries&lt;/span&gt;
&lt;span class="nx"&gt;LibHandlerApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// enable sandbox mode&lt;/span&gt;
&lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__behavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sandBoxMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// create some spreadsheets with data and copy between them&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1h9IGIShgVBVUrUjjawk5MaCEQte_7t32XeEP1Z5jXKQ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;airport list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// add that to sanbox for read without marking it for trashing&lt;/span&gt;
&lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__behavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whitelistFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;copySheetBetweenPlatforms&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-msgraph-libraries&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;

&lt;span class="c1"&gt;// cleanup any files created&lt;/span&gt;
&lt;span class="nx"&gt;ScriptApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__behavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Help us develop gas-fakes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gas-fakes&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;This would be especially helpful if you have Microsoft knowledge and would like to help develop the msgraph connection. Ping me on &lt;a href="mailto:bruce@mcpher.com"&gt;bruce@mcpher.com&lt;/a&gt; if you want to get involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/brucemcpherson/gas-fakes" rel="noopener noreferrer"&gt;gas-fakes&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/brucemcpherson/gas-fakes-containers" rel="noopener noreferrer"&gt;gas-fakes-containers&lt;/a&gt;&lt;br&gt;
More gas-fakes articles: &lt;a href="https://mcpher.com" rel="noopener noreferrer"&gt;desktop liberation&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What is &lt;code&gt;gas-fakes&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;
  
  
  Watch the video
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/oEjpIrkYpEM"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>automation</category>
      <category>javascript</category>
      <category>microsoft</category>
      <category>node</category>
    </item>
  </channel>
</rss>
