@mcpher/gas-fakes 2.3.17 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -32
- package/package.json +1 -2
- package/src/cli/app.js +30 -2
- package/src/cli/server.js +32 -0
- package/src/cli/setup.js +24 -0
- package/src/cli/togas.js +176 -0
- package/src/index.js +2 -0
- package/src/services/common/fakeui.js +45 -0
- package/src/services/content/app.js +3 -0
- package/src/services/content/contentservice.js +14 -0
- package/src/services/content/textoutput.js +45 -0
- package/src/services/documentapp/fakedocumentapp.js +1 -1
- package/src/services/enums/contentenums.js +15 -0
- package/src/services/enums/htmlenums.js +13 -0
- package/src/services/enums/scriptenums.js +6 -0
- package/src/services/formapp/fakeformapp.js +5 -0
- package/src/services/html/app.js +9 -0
- package/src/services/html/consumerworker.js +129 -0
- package/src/services/html/googlescriptrun.js +91 -0
- package/src/services/html/htmloutput.js +127 -0
- package/src/services/html/htmloutputmetatag.js +14 -0
- package/src/services/html/htmlservice.js +94 -0
- package/src/services/html/htmltemplate.js +63 -0
- package/src/services/html/serverworker.js +135 -0
- package/src/services/html/webapp.js +266 -0
- package/src/services/html/worker.js +63 -0
- package/src/services/libhandlerapp/fakelibrary.js +2 -2
- package/src/services/scriptapp/app.js +44 -0
- package/src/services/scriptapp/fakeauthorizationinfo.js +22 -0
- package/src/services/slidesapp/fakeslidesapp.js +5 -0
- package/src/services/spreadsheetapp/fakebooleancondition.js +14 -2
- package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +30 -5
- package/src/services/spreadsheetapp/fakegradientcondition.js +1 -1
- package/src/services/spreadsheetapp/fakeovergridimage.js +25 -0
- package/src/services/spreadsheetapp/fakesheet.js +23 -1
- package/src/services/spreadsheetapp/fakespreadsheet.js +68 -11
- package/src/services/spreadsheetapp/fakespreadsheetapp.js +70 -9
- package/src/services/stores/fakestores.js +5 -0
- package/src/support/auth.js +2 -0
- package/src/support/proxies.js +1 -1
- package/src/support/sxauth.js +20 -12
- package/src/support/utils.js +480 -200
- package/src/support/workersync/sxhtml.js +8 -0
- package/src/support/workersync/synchronizer.js +8 -1
- package/src/support/workersync/worker.js +5 -0
- package/api-docs/kdrive_api.json +0 -69958
- package/appsscript.json +0 -102
- package/gf_agent/README.md +0 -101
- package/gf_agent/SKILL.md +0 -460
- package/gf_agent/documentation.md +0 -105
- package/gf_agent/gf-agent-contributor/SKILL.md +0 -56
- package/gf_agent/index.md +0 -21
- package/gf_agent/knowledge/00-execution-context.md +0 -5
- package/gf_agent/knowledge/01-drive.md +0 -12
- package/gf_agent/knowledge/02-syntax.md +0 -14
- package/gf_agent/knowledge/03-auth.md +0 -15
- package/gf_agent/knowledge/04-advanced.md +0 -46
- package/gf_agent/knowledge/05-sheets-forms.md +0 -26
- package/gf_agent/knowledge/06-jdbc-cloudsql.md +0 -21
- package/gf_agent/knowledge/07-jdbc-auth-details.md +0 -30
- package/gf_agent/knowledge/08-docs-limitations.md +0 -4
- package/gf_agent/knowledge/09-orchestrator-pattern.md +0 -55
- package/gf_agent/knowledge/10-sandbox-security.md +0 -62
- package/gf_agent/knowledge/11-chart-builder-limitations.md +0 -15
- package/gf_agent/knowledge/12-gmail-eventual-consistency.md +0 -13
- package/gf_agent/knowledge/13-advanced-services-discovery.md +0 -23
- package/gf_agent/knowledge/14-utilities-parity.md +0 -13
- package/gf_agent/knowledge/README.md +0 -16
- package/gf_agent/scripts/SKILL.template.md +0 -63
- package/gf_agent/scripts/builder.js +0 -118
- package/gf_agent/skills/base.md +0 -156
- package/gf_agent/skills/cache.md +0 -20
- package/gf_agent/skills/calendar.md +0 -780
- package/gf_agent/skills/charts.md +0 -127
- package/gf_agent/skills/document.md +0 -6752
- package/gf_agent/skills/drive.md +0 -423
- package/gf_agent/skills/forms.md +0 -4036
- package/gf_agent/skills/gmail.md +0 -576
- package/gf_agent/skills/jdbc.md +0 -3101
- package/gf_agent/skills/lock.md +0 -20
- package/gf_agent/skills/properties.md +0 -19
- package/gf_agent/skills/script.md +0 -50
- package/gf_agent/skills/slides.md +0 -5054
- package/gf_agent/skills/spreadsheet.md +0 -56075
- package/gf_agent/skills/urlfetch.md +0 -28
- package/gf_agent/skills/utilities.md +0 -50
- package/gf_agent/skills/xml.md +0 -270
- package/skills-lock.json +0 -10
- package/src/services/documentapp/fakeui.js +0 -27
package/README.md
CHANGED
|
@@ -22,6 +22,16 @@ Collaborators should fork the repo and use the local versions of these files - s
|
|
|
22
22
|
## Usage
|
|
23
23
|
Just as on Apps Script, everything is executed synchronously so you don't need to bother with handling Promises/async/await. Just write normal Apps Script code. Usually you would have an associated App Script project if that's your eventual target. Just like Apps Script, you need a manifest file (appsscript.json) so you can be sure that the correct scopes are authorized and asked for.
|
|
24
24
|
|
|
25
|
+
### Local Web Development (`doGet` / `doPost`)
|
|
26
|
+
You can develop and test Google Apps Script Web Apps and UI Add-ons entirely on your local machine using the built-in local web server. The `gas-fakes serve` command spins up a local HTTP endpoint that routes requests to your `doGet` or `doPost` functions, perfectly emulating the Apps Script environment.
|
|
27
|
+
|
|
28
|
+
It features complete support for:
|
|
29
|
+
- `HtmlService` templating (`<?!= include() ?>`).
|
|
30
|
+
- Client-side RPC via `google.script.run`.
|
|
31
|
+
- Visual UI emulation for Add-ons: Calling `SpreadsheetApp.getUi().showSidebar(html)` automatically frames your HTML output inside a visually accurate Workspace-style sidebar right in your browser!
|
|
32
|
+
|
|
33
|
+
See the [Local Web Development Guide](notes/local-web-development.md) for full details.
|
|
34
|
+
|
|
25
35
|
### Natural Language Automation with `@gf_agent`
|
|
26
36
|
With the introduction of the `gf_agent` skill for Gemini CLI and the built-in MCP server, you can now automate Google Workspace tasks using natural language. This specialized agent understands the full range of `gas-fakes` services and can generate and execute code locally based on your plain English prompts. Whether it's summarizing emails in a Google Doc or analyzing spreadsheet data, you can now do it directly from your terminal using plain English. See the [gf_agent documentation](gf_agent/README.md) for more details.
|
|
27
37
|
|
|
@@ -39,20 +49,7 @@ For details see [gas fakes cli](notes/gas-fakes-cli.md)
|
|
|
39
49
|
|
|
40
50
|
### Configuration
|
|
41
51
|
|
|
42
|
-
Configuration for your local Node environment is handled
|
|
43
|
-
|
|
44
|
-
| Environment Variable | Default | Description |
|
|
45
|
-
|---|---|---|
|
|
46
|
-
| `GF_MANIFEST_PATH` | `./appsscript.json` | Path to the `appsscript.json` manifest file. |
|
|
47
|
-
| `GF_CLASP_PATH` | `./.clasp.json` | Path to the `.clasp.json` file. |
|
|
48
|
-
| `GF_SCRIPT_ID` | from clasp, or random | Discovered from `.clasp.json` or generated as a random UUID during `gas-fakes init`. Used for `ScriptApp.getScriptId()` and partitioning stores. |
|
|
49
|
-
| `GF_DOCUMENT_ID` | `null` | A bound document ID for testing container-bound scripts. |
|
|
50
|
-
| `GF_CACHE_PATH` | `/tmp/gas-fakes/cache` | Path for `CacheService` local file emulation. |
|
|
51
|
-
| `GF_PROPERTIES_PATH` | `/tmp/gas-fakes/properties` | Path for `PropertiesService` local file emulation. |
|
|
52
|
-
| `GF_PLATFORM_AUTH` | `google` | Comma-separated list of backends to initialize (`google`, `ksuite`, `msgraph`). |
|
|
53
|
-
| `AUTH_TYPE` | `dwd` | Google auth type: `dwd` (Domain-Wide Delegation) or `adc` (Application Default Credentials). |
|
|
54
|
-
| `LOG_DESTINATION` | `CONSOLE` | Logging destination: `CONSOLE`, `CLOUD`, `BOTH`, or `NONE`. |
|
|
55
|
-
| `STORE_TYPE` | `FILE` | Internal storage type for properties/cache: `FILE` (local) or `UPSTASH` (Redis). |
|
|
52
|
+
Configuration for your local Node environment is handled interactively by the `gas-fakes init` process, which manages the necessary environment variables in your `.env` file.
|
|
56
53
|
|
|
57
54
|
### Note on Consumer Accounts and ADC
|
|
58
55
|
|
|
@@ -123,21 +120,6 @@ console.log ('....example cloud log link for this session',Logger.__cloudLogLink
|
|
|
123
120
|
|
|
124
121
|
It contains a cloud logging query that will display any logging done in this session - the filter is based on the scriptId (from the environment), the projectId and userId (from Auth), as well as the start and end time of the session.
|
|
125
122
|
|
|
126
|
-
#### A note on .env location
|
|
127
|
-
|
|
128
|
-
You will have used the gas-fakes init command to create a .env file, containing the LOG_DESTINATION setting. You can change any of the settings in the .env file manually if you want to.
|
|
129
|
-
|
|
130
|
-
If you want to set an initial LOG_DESTINATION using that .env file, you have to let gas-fakes know where to find it. Let's assume it's in the same folder as your main script.
|
|
131
|
-
```env
|
|
132
|
-
node yourapp.js
|
|
133
|
-
# or if your .env is somewhere else
|
|
134
|
-
node --env-file pathtoenv yourapp.js
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Alternatively, instead of putting it in an env file, you can export it in your shell environment.
|
|
138
|
-
```sh
|
|
139
|
-
export LOG_DESTINATION="BOTH"
|
|
140
|
-
```
|
|
141
123
|
Finally, another approach is to set it dynamically at the beginning of your app.
|
|
142
124
|
```javascript
|
|
143
125
|
Logger.__logDestination="BOTH"
|
|
@@ -168,14 +150,14 @@ There are a couple of syntactical differences between Node and Apps Script. Not
|
|
|
168
150
|
// this required on Node but not on Apps Script
|
|
169
151
|
if (ScriptApp.isFake) testFakes()
|
|
170
152
|
````
|
|
171
|
-
For
|
|
153
|
+
For pushing modified files back to the Apps Script IDE, use the built-in `gas-fakes togas` CLI command which handles necessary module syntax transformations automatically.
|
|
172
154
|
|
|
173
155
|
|
|
174
156
|
## Help
|
|
175
157
|
|
|
176
158
|
As I mentioned earlier, to take this further, I'm going to need a lot of help to extend the methods and services supported - so if you feel this would be useful to you, and would like to collaborate, please ping me on bruce@mcpher.com and we'll talk.
|
|
177
159
|
|
|
178
|
-
## <img src="
|
|
160
|
+
## <img src="../pngs/logo.png" alt="gas-fakes logo" width="50" align="top"> Further Reading
|
|
179
161
|
|
|
180
162
|
## Watch the gas-fakes intro video
|
|
181
163
|
|
|
@@ -187,6 +169,7 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
|
|
|
187
169
|
|
|
188
170
|
## Read more docs
|
|
189
171
|
|
|
172
|
+
- [release notes](versionnotes/)
|
|
190
173
|
- [gas fakes intro video](https://youtu.be/oEjpIrkYpEM)
|
|
191
174
|
- [getting started](GETTING_STARTED.md) - how to handle authentication for Workspace scopes.
|
|
192
175
|
- [readme](README.md)
|
|
@@ -194,6 +177,7 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
|
|
|
194
177
|
- [Add agent skills to gf_agent](https://ramblings.mcpher.com/add-skills-gf_agent/)
|
|
195
178
|
- [gf_agent documentation](../gf_agent/README.md) - instructions for the Gemini CLI automation agent and MCP server.
|
|
196
179
|
- [gas fakes cli](notes/gas-fakes-cli.md)
|
|
180
|
+
-[local add-on and webapp development with gas-fakes](notes/local-web-development.md)
|
|
197
181
|
- [github actions using adc](https://github.com/brucemcpherson/gas-fakes-actions-adc)
|
|
198
182
|
- [github actions using dwd and wif](https://github.com/brucemcpherson/gas-fakes-actions-dwd)
|
|
199
183
|
- [ksuite as a back end](notes/ksuite_poc.md)
|
|
@@ -227,7 +211,6 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
|
|
|
227
211
|
- [article:using apps script libraries with gas-fakes](https://ramblings.mcpher.com/how-to-use-apps-script-libraries-directly-from-node/)
|
|
228
212
|
- [named range identity](notes/named-range-identity.md)
|
|
229
213
|
- [Workspace scopes with local authentication](notes/workspace_scopes.md)
|
|
230
|
-
- [push test pull](notes/pull-test-push.md)
|
|
231
214
|
- [sharing cache and properties between gas-fakes and live apps script](https://ramblings.mcpher.com/sharing-cache-and-properties-between-gas-fakes-and-live-apps-script/)
|
|
232
215
|
- [gas-fakes-cli now has built in mcp server and gemini extension](https://ramblings.mcpher.com/gas-fakes-cli-now-has-built-in-mcp-server-and-gemini-extension/)
|
|
233
216
|
- [gas-fakes CLI: Run apps script code directly from your terminal](https://ramblings.mcpher.com/gas-fakes-cli-run-apps-script-code-directly-from-your-terminal/)
|
package/package.json
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@azure/identity": "^4.13.1",
|
|
7
7
|
"@mcpher/fake-gasenum": "^1.0.6",
|
|
8
|
-
"@mcpher/gas-fakes": "^2.3.9",
|
|
9
8
|
"@mcpher/gas-flex-cache": "^1.1.5",
|
|
10
9
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
|
11
10
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
@@ -40,7 +39,7 @@
|
|
|
40
39
|
},
|
|
41
40
|
"name": "@mcpher/gas-fakes",
|
|
42
41
|
"author": "bruce mcpherson",
|
|
43
|
-
"version": "2.
|
|
42
|
+
"version": "2.5.1",
|
|
44
43
|
"license": "MIT",
|
|
45
44
|
"main": "main.js",
|
|
46
45
|
"description": "An implementation of the Google Workspace Apps Script runtime: Run native App Script Code on Node and Cloud Run",
|
package/src/cli/app.js
CHANGED
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
authenticateUser,
|
|
10
10
|
enableGoogleAPIs,
|
|
11
11
|
} from "./setup.js";
|
|
12
|
+
import { startWebApp } from "./server.js";
|
|
12
13
|
import { startMcpServer } from "./mcp.js";
|
|
14
|
+
import { togas } from "./togas.js";
|
|
13
15
|
import { Platforms, PlatformDefaults } from "../services/enums/platformenums.js";
|
|
14
16
|
|
|
15
17
|
export async function main() {
|
|
@@ -26,7 +28,7 @@ export async function main() {
|
|
|
26
28
|
|
|
27
29
|
// --- Main Execution Command ---
|
|
28
30
|
program
|
|
29
|
-
.description("Execute a Google Apps Script file or string.")
|
|
31
|
+
.description("Execute a Google Apps Script file or string, or start the server.")
|
|
30
32
|
.option("-f, --filename <string>", "Path to the Google Apps Script file.")
|
|
31
33
|
.option(
|
|
32
34
|
"-s, --script <string>",
|
|
@@ -87,7 +89,7 @@ export async function main() {
|
|
|
87
89
|
if (!hasEnvFileFlag || !isDefaultEnv) {
|
|
88
90
|
const envPath = path.resolve(process.cwd(), env);
|
|
89
91
|
console.log(`...using env file in ${envPath}`);
|
|
90
|
-
dotenv.config({ path: envPath, quiet: true });
|
|
92
|
+
dotenv.config({ path: envPath, quiet: true, override: true });
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
const sandboxConfig = buildSandboxConfig(options);
|
|
@@ -175,6 +177,21 @@ export async function main() {
|
|
|
175
177
|
.option("-t, --tools <string>", "Path to custom tools file.")
|
|
176
178
|
.action(startMcpServer);
|
|
177
179
|
|
|
180
|
+
// --- Server Command ---
|
|
181
|
+
program
|
|
182
|
+
.command("serve [filename]")
|
|
183
|
+
.description("Starts a local web server to handle doGet and doPost requests.")
|
|
184
|
+
.option("-p, --port <number>", "Port for local web server (overrides .env).")
|
|
185
|
+
.option("-e, --env <path>", "Path to a custom .env file.", "./.env")
|
|
186
|
+
.option("-m, --main <string>", "The entry point function to execute on GET requests.", "doGet")
|
|
187
|
+
.action(async (filename, options) => {
|
|
188
|
+
if (!filename) {
|
|
189
|
+
console.error("error: missing required argument 'filename'");
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
await startWebApp({ ...options, filename });
|
|
193
|
+
});
|
|
194
|
+
|
|
178
195
|
// --- JDBC Command ---
|
|
179
196
|
program
|
|
180
197
|
.command("jdbc")
|
|
@@ -193,6 +210,17 @@ export async function main() {
|
|
|
193
210
|
}
|
|
194
211
|
});
|
|
195
212
|
|
|
213
|
+
// --- Togas Command ---
|
|
214
|
+
program
|
|
215
|
+
.command("togas")
|
|
216
|
+
.description("Prepares and pushes local code to Google Apps Script using clasp.")
|
|
217
|
+
.option("-p, --pattern <string>", "Comma-separated glob patterns for files to include (default: *)")
|
|
218
|
+
.option("-t, --target <string>", "Target directory for clasp project (default: TOGAS_TARGET in .env)")
|
|
219
|
+
.option("-s, --source <string>", "Source directory (default: ./ )")
|
|
220
|
+
.option("-e, --env <path>", "Path to a custom .env file.", "./.env")
|
|
221
|
+
.option("--scriptId <string>", "Script ID for the target clasp project.")
|
|
222
|
+
.action(togas);
|
|
223
|
+
|
|
196
224
|
program.showHelpAfterError("(add --help for additional information)");
|
|
197
225
|
|
|
198
226
|
await program.parseAsync(process.argv);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import dotenv from "dotenv";
|
|
4
|
+
import { startServer } from "../services/html/webapp.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Starts a web server to handle doGet and doPost requests.
|
|
8
|
+
*/
|
|
9
|
+
export async function startWebApp(options = {}) {
|
|
10
|
+
const { filename, env, port, main: entryFunction } = options;
|
|
11
|
+
|
|
12
|
+
// Load environment variables
|
|
13
|
+
const envPath = path.resolve(process.cwd(), env || "./.env");
|
|
14
|
+
if (fs.existsSync(envPath)) {
|
|
15
|
+
dotenv.config({ path: envPath, quiet: true, override: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const serverPort = port || process.env.GF_SERVER_PORT || 8080;
|
|
19
|
+
let targetFilename = filename;
|
|
20
|
+
if (targetFilename && !targetFilename.endsWith('.js')) {
|
|
21
|
+
targetFilename += '.js';
|
|
22
|
+
}
|
|
23
|
+
const scriptPath = targetFilename ? path.resolve(process.cwd(), targetFilename) : null;
|
|
24
|
+
|
|
25
|
+
if (!scriptPath || !fs.existsSync(scriptPath)) {
|
|
26
|
+
console.error(`Error: GAS file not found: ${targetFilename}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(`...starting web server for ${scriptPath} (default entrypoint: ${entryFunction})`);
|
|
31
|
+
startServer(serverPort, scriptPath, entryFunction);
|
|
32
|
+
}
|
package/src/cli/setup.js
CHANGED
|
@@ -228,6 +228,30 @@ export async function initializeConfiguration(options = {}) {
|
|
|
228
228
|
name: "GF_PROPERTIES_PATH",
|
|
229
229
|
message: "Properties storage path",
|
|
230
230
|
initial: existingConfig.GF_PROPERTIES_PATH || "/tmp/gas-fakes/properties",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
type: "number",
|
|
234
|
+
name: "GF_SERVER_PORT",
|
|
235
|
+
message: "Port for local web server (doGet/doPost)",
|
|
236
|
+
initial: parseInt(existingConfig.GF_SERVER_PORT) || 8080,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
type: "text",
|
|
240
|
+
name: "TOGAS_TARGET",
|
|
241
|
+
message: "Togas: Target directory for clasp project (e.g., ../testongas)",
|
|
242
|
+
initial: existingConfig.TOGAS_TARGET || "../testongas",
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
type: "text",
|
|
246
|
+
name: "TOGAS_SCRIPT_ID",
|
|
247
|
+
message: "Togas: Script ID for the target clasp project",
|
|
248
|
+
initial: existingConfig.TOGAS_SCRIPT_ID || existingConfig.GF_SCRIPT_ID || "",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
type: "text",
|
|
252
|
+
name: "TOGAS_PATTERN",
|
|
253
|
+
message: "Togas: Comma-separated glob patterns for files to include (e.g. abc*,xyz*)",
|
|
254
|
+
initial: existingConfig.TOGAS_PATTERN || "*",
|
|
231
255
|
}
|
|
232
256
|
];
|
|
233
257
|
|
package/src/cli/togas.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
import { Utils } from "../support/utils.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prepares and pushes local code to Google Apps Script using clasp.
|
|
10
|
+
* @param {object} options Options object provided by commander.js.
|
|
11
|
+
*/
|
|
12
|
+
export async function togas(options) {
|
|
13
|
+
// 1. Load env
|
|
14
|
+
const envPath = path.resolve(process.cwd(), options.env || "./.env");
|
|
15
|
+
if (fs.existsSync(envPath)) {
|
|
16
|
+
dotenv.config({ path: envPath, quiet: true, override: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const target = options.target || process.env.TOGAS_TARGET;
|
|
20
|
+
const scriptId = options.scriptId || process.env.TOGAS_SCRIPT_ID || process.env.GF_SCRIPT_ID;
|
|
21
|
+
const pattern = options.pattern || process.env.TOGAS_PATTERN || "*";
|
|
22
|
+
const source = options.source || "./";
|
|
23
|
+
|
|
24
|
+
if (!target) {
|
|
25
|
+
console.error("Error: TOGAS_TARGET is not set. Please run 'gas-fakes init' or provide --target.");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const absoluteTarget = path.resolve(process.cwd(), target);
|
|
30
|
+
const absoluteSource = path.resolve(process.cwd(), source);
|
|
31
|
+
|
|
32
|
+
if (absoluteTarget === absoluteSource) {
|
|
33
|
+
console.error("Error: Target directory cannot be the same as the source directory.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Prepare target
|
|
38
|
+
if (!fs.existsSync(absoluteTarget)) {
|
|
39
|
+
fs.mkdirSync(absoluteTarget, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const claspJsonPath = path.join(absoluteTarget, ".clasp.json");
|
|
43
|
+
if (!fs.existsSync(claspJsonPath)) {
|
|
44
|
+
const response = await prompts({
|
|
45
|
+
type: "confirm",
|
|
46
|
+
name: "create",
|
|
47
|
+
message: `No .clasp.json found in ${target}. Create one?`,
|
|
48
|
+
initial: true
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (response.create) {
|
|
52
|
+
if (!scriptId) {
|
|
53
|
+
console.error("Error: No Script ID found. Please provide one with --scriptId or in .env.");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const claspConfig = { scriptId, rootDir: "./" };
|
|
57
|
+
fs.writeFileSync(claspJsonPath, JSON.stringify(claspConfig, null, 2));
|
|
58
|
+
console.log(`Created .clasp.json with scriptId: ${scriptId}`);
|
|
59
|
+
} else {
|
|
60
|
+
console.log("Operation cancelled.");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
// Check scriptId
|
|
65
|
+
try {
|
|
66
|
+
const claspConfig = JSON.parse(fs.readFileSync(claspJsonPath, "utf8"));
|
|
67
|
+
if (scriptId && claspConfig.scriptId !== scriptId) {
|
|
68
|
+
console.error(`Error: .clasp.json scriptId (${claspConfig.scriptId}) does not match configured scriptId (${scriptId}).`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.warn(`Warning: Failed to parse ${claspJsonPath}.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 3. Copy files
|
|
77
|
+
console.log(`...scanning source: ${absoluteSource}`);
|
|
78
|
+
console.log(`...matching patterns: ${pattern}`);
|
|
79
|
+
|
|
80
|
+
// Create an array of regexes for the comma-separated patterns
|
|
81
|
+
// We want to match the pattern explicitly ending in .js, .html, or .gs
|
|
82
|
+
const regexPatterns = pattern.split(',').map(p => p.trim()).filter(Boolean).map(p => {
|
|
83
|
+
// Escape regex characters except the * wildcard
|
|
84
|
+
const escapedPattern = p.split('*').map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*');
|
|
85
|
+
|
|
86
|
+
// If the user provided an extension, use it, otherwise match our target extensions
|
|
87
|
+
if (p.endsWith('.js') || p.endsWith('.html') || p.endsWith('.gs')) {
|
|
88
|
+
return new RegExp("^" + escapedPattern + "$");
|
|
89
|
+
} else {
|
|
90
|
+
// Append the target extensions to the pattern
|
|
91
|
+
return new RegExp("^" + escapedPattern + "\\.(js|html|gs)$");
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const copiedFiles = [];
|
|
96
|
+
const specialFiles = ["appsscript.json"];
|
|
97
|
+
|
|
98
|
+
// Helper to walk directory recursively and efficiently
|
|
99
|
+
function walkSync(dir, relativeDir = "") {
|
|
100
|
+
const absoluteDir = path.join(dir, relativeDir);
|
|
101
|
+
|
|
102
|
+
// Skip target directory if it's inside source
|
|
103
|
+
if (absoluteDir === absoluteTarget) return;
|
|
104
|
+
|
|
105
|
+
const entries = fs.readdirSync(absoluteDir, { withFileTypes: true });
|
|
106
|
+
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const entryRelativePath = path.join(relativeDir, entry.name);
|
|
109
|
+
const entryAbsolutePath = path.join(dir, entryRelativePath);
|
|
110
|
+
|
|
111
|
+
if (entry.isDirectory()) {
|
|
112
|
+
// Skip common ignored directories
|
|
113
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entryAbsolutePath === absoluteTarget) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
walkSync(dir, entryRelativePath);
|
|
117
|
+
} else if (entry.isFile()) {
|
|
118
|
+
const fileName = entry.name;
|
|
119
|
+
const extension = path.extname(fileName).toLowerCase();
|
|
120
|
+
|
|
121
|
+
// Handle special files
|
|
122
|
+
if (specialFiles.includes(entryRelativePath)) {
|
|
123
|
+
const destPath = path.join(absoluteTarget, entryRelativePath);
|
|
124
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
125
|
+
fs.copyFileSync(entryAbsolutePath, destPath);
|
|
126
|
+
if (extension === ".js" || extension === ".gs") copiedFiles.push(destPath);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Match patterns for .js, .gs, .html
|
|
131
|
+
if ((extension === ".js" || extension === ".html" || extension === ".gs") &&
|
|
132
|
+
regexPatterns.some(regex => regex.test(fileName))) {
|
|
133
|
+
|
|
134
|
+
const destPath = path.join(absoluteTarget, entryRelativePath);
|
|
135
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
136
|
+
fs.copyFileSync(entryAbsolutePath, destPath);
|
|
137
|
+
if (extension === ".js" || extension === ".gs") copiedFiles.push(destPath);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
walkSync(absoluteSource);
|
|
144
|
+
|
|
145
|
+
if (copiedFiles.length === 0) {
|
|
146
|
+
console.log("No files matched the patterns. Nothing to do.");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 4. Transform JS files
|
|
151
|
+
console.log(`...transforming ${copiedFiles.length} files for Apps Script compatibility`);
|
|
152
|
+
for (const filePath of copiedFiles) {
|
|
153
|
+
let content = fs.readFileSync(filePath, "utf8");
|
|
154
|
+
fs.writeFileSync(filePath, Utils.stripEsmKeywords(content));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(`Ready for clasp push in ${target}.`);
|
|
158
|
+
|
|
159
|
+
// 5. Clasp push
|
|
160
|
+
const response = await prompts({
|
|
161
|
+
type: "confirm",
|
|
162
|
+
name: "push",
|
|
163
|
+
message: "Push to Apps Script using clasp now?",
|
|
164
|
+
initial: true
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (response.push) {
|
|
168
|
+
console.log("Running clasp push...");
|
|
169
|
+
try {
|
|
170
|
+
execSync("clasp push", { cwd: absoluteTarget, stdio: "inherit" });
|
|
171
|
+
console.log("Push successful.");
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error("Clasp push failed. Ensure you are logged in (clasp login) and the script exists.");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Provides a fake implementation of the Ui class.
|
|
3
|
+
*/
|
|
4
|
+
import { Proxies } from '../../support/proxies.js';
|
|
5
|
+
import { notYetImplemented } from '../../support/helpers.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A fake implementation of the Ui class.
|
|
9
|
+
* @class FakeUi
|
|
10
|
+
* @implements {GoogleAppsScript.Base.Ui}
|
|
11
|
+
*/
|
|
12
|
+
class FakeUi {
|
|
13
|
+
createAddonMenu() {
|
|
14
|
+
return notYetImplemented('Ui.createAddonMenu');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
createMenu(caption) {
|
|
18
|
+
return notYetImplemented('Ui.createMenu');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
showSidebar(userInterface) {
|
|
22
|
+
// In live GAS, this returns void and pushes to the client UI.
|
|
23
|
+
// Locally, we set the dimensions to match a standard Sidebar (300px width).
|
|
24
|
+
if (userInterface && typeof userInterface.setWidth === 'function') {
|
|
25
|
+
userInterface.setWidth(300);
|
|
26
|
+
userInterface.__framingType = 'sidebar';
|
|
27
|
+
}
|
|
28
|
+
// We log it so the developer knows the UI interaction occurred.
|
|
29
|
+
console.log(`[gas-fakes] Ui.showSidebar triggered with Title: "${userInterface.getTitle ? userInterface.getTitle() : ''}"`);
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
showModalDialog(userInterface, title) {
|
|
34
|
+
// In live GAS, this returns void and pushes to the client UI.
|
|
35
|
+
// Locally, we set the title and apply standard dialog styling to the HtmlOutput.
|
|
36
|
+
if (userInterface && typeof userInterface.setTitle === 'function') {
|
|
37
|
+
userInterface.setTitle(title);
|
|
38
|
+
userInterface.__framingType = 'modal';
|
|
39
|
+
}
|
|
40
|
+
console.log(`[gas-fakes] Ui.showModalDialog triggered with Title: "${title}"`);
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const newFakeUi = (...args) => Proxies.guard(new FakeUi(...args));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FakeTextOutput } from "./textoutput.js";
|
|
2
|
+
import { ContentEnums } from "../enums/contentenums.js";
|
|
3
|
+
|
|
4
|
+
export class FakeContentService {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.MimeType = ContentEnums.MimeType;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
createTextOutput(content = "") {
|
|
10
|
+
return new FakeTextOutput(content);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const newFakeContentService = () => new FakeContentService();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class FakeTextOutput {
|
|
2
|
+
constructor(content = "") {
|
|
3
|
+
this._content = content;
|
|
4
|
+
this._mimeType = "text/plain";
|
|
5
|
+
this._fileName = null;
|
|
6
|
+
this.__isTextOutput = true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
append(content) {
|
|
10
|
+
this._content += content;
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
clear() {
|
|
15
|
+
this._content = null;
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
downloadAsFile(filename) {
|
|
20
|
+
this._fileName = filename;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getContent() {
|
|
25
|
+
return this._content;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getFileName() {
|
|
29
|
+
return this._fileName;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getMimeType() {
|
|
33
|
+
return this._mimeType;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setContent(content) {
|
|
37
|
+
this._content = content;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setMimeType(mimeType) {
|
|
42
|
+
this._mimeType = mimeType;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -3,7 +3,7 @@ import { newFakeDocument } from './fakedocument.js';
|
|
|
3
3
|
|
|
4
4
|
import { signatureArgs } from '../../support/helpers.js';
|
|
5
5
|
import is from '@sindresorhus/is';
|
|
6
|
-
import { newFakeUi } from '
|
|
6
|
+
import { newFakeUi } from '../common/fakeui.js';
|
|
7
7
|
import { Auth } from '../../support/auth.js';
|
|
8
8
|
import * as Enums from '../enums/docsenums.js'
|
|
9
9
|
import { defaultDocumentStyleRequests } from './elementblasters.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Proxies } from '../../support/proxies.js';
|
|
2
2
|
import * as Enums from '../enums/formsenums.js';
|
|
3
3
|
import { newFakeForm } from './fakeform.js';
|
|
4
|
+
import { newFakeUi } from '../common/fakeui.js';
|
|
4
5
|
import { Auth } from '../../support/auth.js';
|
|
5
6
|
import { Url } from '../../support/url.js';
|
|
6
7
|
import { signatureArgs } from '../../support/helpers.js'
|
|
@@ -74,6 +75,10 @@ class FakeFormApp {
|
|
|
74
75
|
return this.openById(id);
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
getUi() {
|
|
79
|
+
return newFakeUi();
|
|
80
|
+
}
|
|
81
|
+
|
|
77
82
|
/**
|
|
78
83
|
* Opens the form with the specified ID.
|
|
79
84
|
* @param {string} id The ID of the form to open.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* the idea here is to create an empty global entry for the singleton
|
|
4
|
+
* but only load it when it is actually used.
|
|
5
|
+
*/
|
|
6
|
+
import { lazyLoaderApp } from '../common/lazyloader.js'
|
|
7
|
+
import { newFakeHtmlService as maker} from './htmlservice.js'
|
|
8
|
+
let _app = null;
|
|
9
|
+
_app = lazyLoaderApp(_app, 'HtmlService', maker)
|