@mcpher/gas-fakes 1.2.7 → 1.2.9

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 CHANGED
@@ -169,4 +169,5 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
169
169
  - [named range identity](named-range-identity.md)
170
170
  - [adc and restricted scopes](https://ramblings.mcpher.com/how-to-allow-access-to-sensitive-scopes-with-application-default-credentials/)
171
171
  - [push test pull](pull-test-push.md)
172
- - [gas fakes cli](gas-fakes-cli.md)
172
+ - [gas fakes cli](gas-fakes-cli.md)
173
+ - [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/)
package/gas-fakes.js CHANGED
@@ -2,14 +2,18 @@
2
2
 
3
3
  /**
4
4
  * cli for gas-fakes
5
- * v0.0.1
6
5
  */
7
6
  import fs from "fs";
8
7
  import path from "path";
9
8
  import { Command } from "commander";
10
- import dotenv from 'dotenv'
9
+ import dotenv from "dotenv";
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
+ import z from "zod";
13
+ import { exec } from "child_process";
14
+ import { promisify } from "util";
11
15
 
12
- const version = "0.0.2";
16
+ const version = "0.0.3";
13
17
 
14
18
  const program = new Command();
15
19
 
@@ -18,7 +22,6 @@ program
18
22
  .description("CLI tool for gas-fakes")
19
23
  .version(version, "-v, --version", "display the current version");
20
24
 
21
-
22
25
  program
23
26
  .description("Execute Google Apps Script using gas-fakes.")
24
27
  .option(
@@ -27,11 +30,13 @@ program
27
30
  )
28
31
  .option(
29
32
  "-e, --env <path>",
30
- "provide path to your .env file for special options."
33
+ "provide path to your .env file for special options.",
34
+ "./.env"
31
35
  )
32
36
  .option(
33
37
  "-g, --gfsettings <path>",
34
- "provide path to your gasfakes.json file for script options."
38
+ "provide path to your gasfakes.json file for script options.",
39
+ "./gasfakes.json"
35
40
  )
36
41
  .option(
37
42
  "-s, --script <string>",
@@ -55,24 +60,44 @@ program
55
60
  if (Object.keys(options).length == 0) {
56
61
  program.help();
57
62
  } else {
58
- const { filename, script, sandbox, whitelist, json, display, env , gfsettings} = options;
63
+ const {
64
+ filename,
65
+ script,
66
+ sandbox,
67
+ whitelist,
68
+ json,
69
+ display,
70
+ env,
71
+ gfsettings,
72
+ } = options;
59
73
  const obj = { sandbox: !!sandbox, display };
60
- if (!filename && !script) {
74
+
75
+ if (!filename && !script && !obj.script) {
61
76
  console.error(
62
77
  "error: Provide the filename or the script of Google Apps Script."
63
78
  );
64
79
  process.exit();
65
80
  }
66
- if (gfsettings) {
67
- obj.gfSettings = path.resolve(gfsettings);
68
- }
81
+
69
82
  if (env) {
70
- dotenv.config({ path: path.resolve(env) });
83
+ const envPath = path.resolve(process.cwd(), env);
84
+ console.log("...using env file in", envPath);
85
+ dotenv.config({ path: envPath, quiet: true });
86
+ }
87
+
88
+ // note this must come after any env file fiddling.
89
+ if (gfsettings) {
90
+ const gfPath = path.resolve(process.cwd(), gfsettings);
91
+ console.log("...using gasfakes settings file in", gfPath);
92
+ obj.gfSettings = gfPath;
93
+ // override whatever is in env
94
+ process.env.GF_SETTINGS_PATH = gfPath;
71
95
  }
96
+
72
97
  if (filename) {
73
98
  obj.filename = filename;
74
99
  }
75
- if (script) {
100
+ if (!obj.script && script) {
76
101
  obj.script = script;
77
102
  }
78
103
 
@@ -96,15 +121,22 @@ program
96
121
  }
97
122
  });
98
123
 
124
+ const execAsync = promisify(exec);
125
+ program
126
+ .command("mcp")
127
+ .description("Launch gas-fakes as the MCP server")
128
+ .action(mcp_server);
129
+
99
130
  program.showHelpAfterError("(add --help for additional information)");
100
131
  program.parse();
101
132
 
102
133
  function __getImportScript(o) {
103
- const { scriptText, sandbox, whitelistItems, json_sandbox, gfSettings } = o;
134
+ const { scriptText, sandbox, whitelistItems, json_sandbox } = o;
104
135
  if (scriptText.trim() == "") {
105
136
  console.error("error: Google Apps Script was not found.");
106
137
  process.exit();
107
138
  }
139
+ let gasScriptStr = "";
108
140
  const gasScriptAr = [];
109
141
  if (json_sandbox) {
110
142
  gasScriptAr.push(
@@ -167,8 +199,7 @@ function __getImportScript(o) {
167
199
  `const behavior = ScriptApp.__behavior;`,
168
200
  `behavior.sandboxMode = true;`,
169
201
  `behavior.strictSandbox = true;`,
170
- `behavior.setIdWhitelist([${wl}]);`,
171
- `\n\n${scriptText}\n\n`,
202
+ `behavior.setIdWhitelist([${wl}]);``\n\n${scriptText}\n\n`,
172
203
  `ScriptApp.__behavior.trash();`
173
204
  );
174
205
  } else {
@@ -177,10 +208,6 @@ function __getImportScript(o) {
177
208
  }
178
209
  const importScriptAr = [
179
210
  `async function runGas() {`,
180
- // Pass the settings path to the init function if it's provided
181
- gfSettings
182
- ? `const settingsPath = "${gfSettings}";`
183
- : `const settingsPath = undefined;`,
184
211
  `await import("./main.js");`, // This will trigger the fxInit call
185
212
  ...gasScriptAr,
186
213
  `};`,
@@ -195,13 +222,13 @@ function __getImportScript(o) {
195
222
 
196
223
  async function loadScript(o) {
197
224
  const { filename, script, display } = o;
198
-
199
225
  const scriptText = filename ? fs.readFileSync(filename, "utf8") : script;
200
- const { mainScript, gasScript } = __getImportScript({ scriptText, ...o });
226
+ const { mainScript, gasScript } = __getImportScript({
227
+ scriptText: scriptText.replace(/\\n/g, "\n"),
228
+ ...o,
229
+ });
201
230
  if (display) {
202
- console.log(`--- script ---`);
203
- console.log(gasScript);
204
- console.log(`--- /script ---`);
231
+ console.log(`\n--- script ---\n${gasScript}\n--- /script ---\n`);
205
232
  }
206
233
  const gasFunc = new Function(mainScript);
207
234
  // The script needs access to the settings path variable we just created
@@ -212,3 +239,71 @@ async function loadScript(o) {
212
239
  });
213
240
  gasFunc();
214
241
  }
242
+
243
+ async function mcp_server() {
244
+ const server = new McpServer({
245
+ name: "gas-fakes-mcp",
246
+ version: "0.0.1",
247
+ });
248
+
249
+ const { name, schema, func } = {
250
+ name: "run-gas-by-gas-fakes",
251
+ schema: {
252
+ description:
253
+ "Use this to safely run Google Apps Script in a sandbox using gas-fakes.",
254
+ inputSchema: {
255
+ script: z.string().describe(`Provide Google Apps Script as a string.`),
256
+ sandbox: z
257
+ .boolean()
258
+ .describe("Use to run Google Apps Script in a sandbox."),
259
+ whitelist: z
260
+ .string()
261
+ .describe(
262
+ "Use this to use the specific files and folders on Google Drive. whitelist of file IDs. Set the file IDs in comma-separated list. In this case, the files of the file IDs are used for both read and write. When this is used, the script is run in a sandbox."
263
+ )
264
+ .optional(),
265
+ json: z
266
+ .string()
267
+ .describe(
268
+ `Use this to manage the sandbox more if the detailed information about the sandbox is provided. JSON string including parameters for managing a sandbox. Enclose it with ' or ". When this is used, the option "whitelist" is ignored. When this is used, the script is run in a sandbox.`
269
+ )
270
+ .optional(),
271
+ },
272
+ },
273
+ func: async (options = {}) => {
274
+ const { sandbox, whitelist, json } = options;
275
+ try {
276
+ const opts = [
277
+ { v: sandbox, k: "-x" },
278
+ { v: whitelist, k: "-w" },
279
+ { v: json, k: "-j" },
280
+ ].reduce((ar, { v, k }) => {
281
+ if (v) {
282
+ ar.push(k != "-x" ? `${k} ${v}` : `${k}`);
283
+ }
284
+ return ar;
285
+ }, []);
286
+ const scriptArg = JSON.stringify(options.script.toString());
287
+ const c = `gas-fakes ${opts.join(" ")} -s ${scriptArg.replace(
288
+ /\\n/g,
289
+ "\n"
290
+ )}`;
291
+ const { stdout } = await execAsync(c);
292
+ return {
293
+ content: [{ type: "text", text: stdout || "Done." }],
294
+ isError: false,
295
+ };
296
+ } catch (err) {
297
+ return {
298
+ content: [{ type: "text", text: err.message }],
299
+ isError: true,
300
+ };
301
+ }
302
+ },
303
+ };
304
+
305
+ server.registerTool(name, schema, func);
306
+
307
+ const transport = new StdioServerTransport();
308
+ await server.connect(transport);
309
+ }
package/gasfakes.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "manifest": "./appsscript.json",
3
+ "clasp": "./.clasp.json",
4
+ "scriptId": "28780abe-aaec-4526-a0bf-9d5c104c68b8",
5
+ "documentId": null,
6
+ "cache": "/tmp/gas-fakes/cache",
7
+ "properties": "/tmp/gas-fakes/properties"
8
+ }
package/package.json CHANGED
@@ -5,6 +5,7 @@
5
5
  "dependencies": {
6
6
  "@mcpher/fake-gasenum": "^1.0.2",
7
7
  "@mcpher/gas-flex-cache": "^1.1.2",
8
+ "@modelcontextprotocol/sdk": "^1.20.2",
8
9
  "@sindresorhus/is": "^7.0.1",
9
10
  "archiver": "^7.0.1",
10
11
  "commander": "^14.0.1",
@@ -24,58 +25,16 @@
24
25
  "subsume": "^4.0.0",
25
26
  "to-readable-stream": "^4.0.0",
26
27
  "unzipper": "^0.12.3",
27
- "yoctocolors": "^2.1.2"
28
+ "yoctocolors": "^2.1.2",
29
+ "zod": "^3.25.76"
28
30
  },
29
31
  "type": "module",
30
32
  "scripts": {
31
- "test": "node --env-file=./.env ./test/test.js",
32
- "testdrive": "node --env-file=./.env ./test/testdrive.js execute",
33
- "testsheetsdatavalidations": "node --env-file=./.env ./test/testsheetsdatavalidations.js execute",
34
- "testsheetspermissions": "node --env-file=./.env ./test/testsheetspermissions.js execute",
35
- "testsheetsvalues": "node --env-file=./.env ./test/testsheetsvalues.js execute",
36
- "testsheets": "node --env-file=./.env ./test/testsheets.js execute",
37
- "testfetch": "node --env-file=./.env ./test/testfetch.js execute",
38
- "testsession": "node --env-file=./.env ./test/testsession.js execute",
39
- "testutilities": "node --env-file=./.env ./test/testutilities.js execute",
40
- "teststores": "node --env-file=./.env ./test/teststores.js execute",
41
- "testscriptapp": "node --env-file=./.env ./test/testscriptapp.js execute",
42
- "testfiddler": "node --env-file=./.env ./test/testfiddler.js execute",
43
- "testenums": "node --env-file=./.env ./test/testenums.js execute",
44
- "testsheetssets": "node --env-file=./.env ./test/testsheetssets.js execute",
45
- "testsheetsvui": "node --env-file=./.env ./test/testsheetsvui.js execute",
46
- "testsheetsdeveloper": "node --env-file=./.env ./test/testsheetsdeveloper.js execute",
47
- "testsheetsexotics": "node --env-file=./.env ./test/testsheetsexotics.js execute",
48
- "testsheetsdata": "node --env-file=./.env ./test/testsheetsdata.js execute",
49
- "testdocsadv": "node --env-file=./.env ./test/testdocsadv.js execute",
50
- "testslidesadv": "node --env-file=./.env ./test/testslidesadv.js execute",
51
- "testform": "node --env-file=./.env ./test/testform.js execute",
52
- "testformsadv": "node --env-file=./.env ./test/testformsadv.js execute",
53
- "testdocs": "node --env-file=./.env ./test/testdocs.js execute",
54
- "testslides": "node --env-file=./.env ./test/testslides.js execute",
55
- "testdocsnext": "node --env-file=./.env ./test/testdocsnext.js execute",
56
- "testsheetstext": "node --env-file=./.env ./test/testsheetstext.js execute",
57
- "testsheetsrange": "node --env-file=./.env ./test/testsheetsrange.js execute",
58
- "testdocslistitems": "node --env-file=./.env ./test/testdocslistitems.js execute",
59
- "testdocsall": "node --env-file=./.env ./test/testdocsall.js",
60
- "testdocsheaders": "node --env-file=./.env ./test/testdocsheaders.js execute",
61
- "testdocsfooters": "node --env-file=./.env ./test/testdocsfooters.js execute",
62
- "testdocsfootnotes": "node --env-file=./.env ./test/testdocsfootnotes.js execute",
63
- "testdocsimages": "node --env-file=./.env ./test/testdocsimages.js execute",
64
- "testdocsstyles": "node --env-file=./.env ./test/testdocsstyles.js execute",
65
- "testsandbox": "node --trace-warnings ./test/testsandbox.js execute",
66
- "testgmail": "node --env-file=./.env ./test/testgmail.js execute",
67
- "testlogger": "node --env-file=./.env ./test/testlogger.js execute",
68
- "testchat": "node --env-file=./.env ./test/testchat.js execute",
69
- "testpeople": "node --env-file=./.env ./test/testpeople.js execute",
70
- "testtasks": "node --env-file=./.env ./test/testtasks.js execute",
71
- "testcalendar": "node --env-file=./.env ./test/testcalendar.js execute",
72
- "testworkspaceevents": "node --env-file=./.env ./test/testworkspaceevents.js execute",
73
- "testmimetype": "node --env-file=./.env ./test/testmimetype.js execute",
74
33
  "pub": "npm publish --access public"
75
34
  },
76
35
  "name": "@mcpher/gas-fakes",
77
36
  "author": "bruce mcpherson",
78
- "version": "1.2.7",
37
+ "version": "1.2.9",
79
38
  "license": "MIT",
80
39
  "main": "main.js",
81
40
  "description": "A proof of concept implementation of Apps Script Environment on Node",
@@ -20,13 +20,12 @@ import path from 'path'
20
20
  * @param {string} p.authPath import the auth code
21
21
  * @param {string} p.claspPath where to find the clasp file by default
22
22
  * @param {string} p.settingsPath where to find the settings file
23
- * @param {string} p.mainDir the directory the main app is running from
24
23
  * @param {string} p.cachePath the cache files
25
24
  * @param {string} p.propertiesPath the properties file location
26
25
  * @param {string} p.fakeId a fake script id to use if one isnt in the settings
27
26
  * @return {object} the finalized vesions of all the above
28
27
  */
29
- export const sxInit = async ({ manifestPath, claspPath, settingsPath, mainDir, cachePath, propertiesPath, fakeId }) => {
28
+ export const sxInit = async ({ manifestPath, claspPath, settingsPath, cachePath, propertiesPath, fakeId }) => {
30
29
 
31
30
 
32
31
 
@@ -47,13 +46,11 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, mainDir, c
47
46
  }
48
47
 
49
48
  // files are relative to this main path
49
+ const settingsDir = path.dirname(settingsPath)
50
50
 
51
- const settingsFile = path.resolve(mainDir, settingsPath)
52
- const settingsDir = path.dirname(settingsFile)
53
-
54
- // syncLog (JSON.stringify({mainDir,settingsPath,settingsDir,settingsFile}))
51
+ // syncLog (JSON.stringify({settingsPath,settingsDir,settingsPath}))
55
52
  // get the setting file if it exists
56
- const _settings = await getIfExists(path.resolve(mainDir, settingsFile))
53
+ const _settings = await getIfExists(settingsPath)
57
54
  const settings = { ..._settings }
58
55
 
59
56
  // the content of the settings file take precedence over whatever is passed as the default
@@ -61,8 +58,8 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, mainDir, c
61
58
  settings.manifest = settings.manifest || manifestPath
62
59
  settings.clasp = settings.clasp || claspPath
63
60
  const [manifest, clasp] = await Promise.all([
64
- getIfExists(path.resolve(mainDir, settings.manifest)),
65
- getIfExists(path.resolve(mainDir, settings.clasp))
61
+ getIfExists(path.resolve(settingsDir, settings.manifest)),
62
+ getIfExists(path.resolve(settingsDir, settings.clasp))
66
63
  ])
67
64
 
68
65
  /// if we dont have a scriptId we need to check in clasp or make a fakeone
@@ -82,8 +79,8 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, mainDir, c
82
79
  const strSet = JSON.stringify(settings, null, 2)
83
80
  if (JSON.stringify(_settings, null, 2) !== strSet) {
84
81
  await mkdir(settingsDir, { recursive: true })
85
- syncLog(`...writing to ${settingsFile}`);
86
- writeFile(settingsFile, strSet, { flag: 'w' })
82
+ syncLog(`...writing to ${settingsPath}`);
83
+ writeFile(settingsPath, strSet, { flag: 'w' })
87
84
  }
88
85
 
89
86
  // get the required scopes and set them
@@ -262,10 +262,11 @@ const fxUnzipper = ({ blob }) => {
262
262
  const fxInit = ({
263
263
  manifestPath = manifestDefaultPath,
264
264
  claspPath = claspDefaultPath,
265
- settingsPath = globalThis.settingsPath || settingsDefaultPath,
265
+ settingsPath = settingsDefaultPath,
266
266
  cachePath = cacheDefaultPath,
267
267
  propertiesPath = propertiesDefaultPath,
268
268
  } = {}) => {
269
+
269
270
  // this is the path of the runing main process
270
271
  const mainDir = path.dirname(process.argv[1]);
271
272