@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 +2 -1
- package/gas-fakes.js +120 -25
- package/gasfakes.json +8 -0
- package/package.json +4 -45
- package/src/support/sxauth.js +8 -11
- package/src/support/syncit.js +2 -1
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
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
obj.gfSettings = path.resolve(gfsettings);
|
|
68
|
-
}
|
|
81
|
+
|
|
69
82
|
if (env) {
|
|
70
|
-
|
|
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
|
|
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({
|
|
226
|
+
const { mainScript, gasScript } = __getImportScript({
|
|
227
|
+
scriptText: scriptText.replace(/\\n/g, "\n"),
|
|
228
|
+
...o,
|
|
229
|
+
});
|
|
201
230
|
if (display) {
|
|
202
|
-
console.log(
|
|
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
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.
|
|
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",
|
package/src/support/sxauth.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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(
|
|
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(
|
|
65
|
-
getIfExists(path.resolve(
|
|
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 ${
|
|
86
|
-
writeFile(
|
|
82
|
+
syncLog(`...writing to ${settingsPath}`);
|
|
83
|
+
writeFile(settingsPath, strSet, { flag: 'w' })
|
|
87
84
|
}
|
|
88
85
|
|
|
89
86
|
// get the required scopes and set them
|
package/src/support/syncit.js
CHANGED
|
@@ -262,10 +262,11 @@ const fxUnzipper = ({ blob }) => {
|
|
|
262
262
|
const fxInit = ({
|
|
263
263
|
manifestPath = manifestDefaultPath,
|
|
264
264
|
claspPath = claspDefaultPath,
|
|
265
|
-
settingsPath =
|
|
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
|
|