@mcpher/gas-fakes 1.2.17 → 1.2.19
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.RU.md +3 -2
- package/README.md +3 -2
- package/gas-fakes.js +18 -2
- package/package.json +1 -1
- package/setup.js +413 -128
- package/src/services/advdocs/fakeadvdocuments.js +0 -1
- package/src/services/common/lazyloader.js +1 -2
- package/src/services/documentapp/elementhelpers.js +3 -3
- package/src/services/documentapp/shadowdocument.js +2 -2
- package/src/services/driveapp/fakedrivemeta.js +2 -2
- package/src/services/logger/fakelogger.js +1 -1
- package/src/services/scriptapp/app.js +2 -1
- package/src/services/scriptapp/behavior.js +5 -4
- package/src/services/spreadsheetapp/fakesheet.js +2 -4
- package/src/services/spreadsheetapp/sheetrangehelpers.js +2 -2
- package/src/services/stores/fakestores.js +2 -3
- package/src/support/fakegasenum.js +1 -1
- package/src/support/helpers.js +3 -3
- package/src/support/proxies.js +2 -2
- package/src/support/slogger.js +14 -0
- package/src/support/utils.js +3 -3
- package/src/support/workersync/synchronizer.js +2 -1
- package/src/support/workersync/synclogger.js +5 -2
package/README.RU.md
CHANGED
|
@@ -332,7 +332,7 @@ const getParentsIterator = ({
|
|
|
332
332
|
- [named colors](named-colors.md)
|
|
333
333
|
- [sandbox](sandbox.md)
|
|
334
334
|
|
|
335
|
-
## <img src="./logo.png" alt="gas-fakes logo" width="50" align="top">
|
|
335
|
+
## <img src="./logo.png" alt="gas-fakes logo" width="50" align="top"> Further Reading
|
|
336
336
|
|
|
337
337
|
- [getting started](GETTING_STARTED.md) - how to handle authentication for restricted scopes.
|
|
338
338
|
- [readme](README.md)
|
|
@@ -359,8 +359,9 @@ const getParentsIterator = ({
|
|
|
359
359
|
- [Supercharge Your Google Apps Script Caching with GasFlexCache](https://ramblings.mcpher.com/supercharge-your-google-apps-script-caching-with-gasflexcache/)
|
|
360
360
|
- [Fake-Sandbox for Google Apps Script: Granular controls.](https://ramblings.mcpher.com/fake-sandbox-for-google-apps-script-granular-controls/)
|
|
361
361
|
- [A Fake-Sandbox for Google Apps Script: Securely Executing Code Generated by Gemini CLI](https://ramblings.mcpher.com/gas-fakes-sandbox/)
|
|
362
|
+
- [Modern Google Apps Script Workflow Building on the Cloud](https://medium.com/google-cloud/modern-google-apps-script-workflow-building-on-the-cloud-2255dbd32ac3)
|
|
362
363
|
- [Bridging the Gap: Seamless Integration for Local Google Apps Script Development](https://medium.com/@tanaike/bridging-the-gap-seamless-integration-for-local-google-apps-script-development-9b9b973aeb02)
|
|
363
364
|
- [Next-Level Google Apps Script Development](https://medium.com/google-cloud/next-level-google-apps-script-development-654be5153912)
|
|
364
365
|
- [Secure and Streamlined Google Apps Script Development with gas-fakes CLI and Gemini CLI Extension](https://medium.com/google-cloud/secure-and-streamlined-google-apps-script-development-with-gas-fakes-cli-and-gemini-cli-extension-67bbce80e2c8)
|
|
365
366
|
- [Secure and Conversational Google Workspace Automation: Integrating Gemini CLI with a gas-fakes MCP Server](https://medium.com/google-cloud/secure-and-conversational-google-workspace-automation-integrating-gemini-cli-with-a-gas-fakes-mcp-0a5341559865)
|
|
366
|
-
- [A Fake-Sandbox for Google Apps Script: A Feasibility Study on Securely Executing Code Generated by Gemini CL](https://medium.com/google-cloud/a-fake-sandbox-for-google-apps-script-a-feasibility-study-on-securely-executing-code-generated-by-cc985ce5dae3)
|
|
367
|
+
- [A Fake-Sandbox for Google Apps Script: A Feasibility Study on Securely Executing Code Generated by Gemini CL](https://medium.com/google-cloud/a-fake-sandbox-for-google-apps-script-a-feasibility-study-on-securely-executing-code-generated-by-cc985ce5dae3)
|
package/README.md
CHANGED
|
@@ -151,7 +151,7 @@ For inspiration on pushing modified files to the IDE, see the togas.sh bash scri
|
|
|
151
151
|
|
|
152
152
|
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.
|
|
153
153
|
|
|
154
|
-
## <img src="./logo.png" alt="gas-fakes logo" width="50" align="top">
|
|
154
|
+
## <img src="./logo.png" alt="gas-fakes logo" width="50" align="top"> Further Reading
|
|
155
155
|
|
|
156
156
|
- [getting started](GETTING_STARTED.md) - how to handle authentication for restricted scopes.
|
|
157
157
|
- [readme](README.md)
|
|
@@ -178,8 +178,9 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
|
|
|
178
178
|
- [Supercharge Your Google Apps Script Caching with GasFlexCache](https://ramblings.mcpher.com/supercharge-your-google-apps-script-caching-with-gasflexcache/)
|
|
179
179
|
- [Fake-Sandbox for Google Apps Script: Granular controls.](https://ramblings.mcpher.com/fake-sandbox-for-google-apps-script-granular-controls/)
|
|
180
180
|
- [A Fake-Sandbox for Google Apps Script: Securely Executing Code Generated by Gemini CLI](https://ramblings.mcpher.com/gas-fakes-sandbox/)
|
|
181
|
+
- [Modern Google Apps Script Workflow Building on the Cloud](https://medium.com/google-cloud/modern-google-apps-script-workflow-building-on-the-cloud-2255dbd32ac3)
|
|
181
182
|
- [Bridging the Gap: Seamless Integration for Local Google Apps Script Development](https://medium.com/@tanaike/bridging-the-gap-seamless-integration-for-local-google-apps-script-development-9b9b973aeb02)
|
|
182
183
|
- [Next-Level Google Apps Script Development](https://medium.com/google-cloud/next-level-google-apps-script-development-654be5153912)
|
|
183
184
|
- [Secure and Streamlined Google Apps Script Development with gas-fakes CLI and Gemini CLI Extension](https://medium.com/google-cloud/secure-and-streamlined-google-apps-script-development-with-gas-fakes-cli-and-gemini-cli-extension-67bbce80e2c8)
|
|
184
185
|
- [Secure and Conversational Google Workspace Automation: Integrating Gemini CLI with a gas-fakes MCP Server](https://medium.com/google-cloud/secure-and-conversational-google-workspace-automation-integrating-gemini-cli-with-a-gas-fakes-mcp-0a5341559865)
|
|
185
|
-
- [A Fake-Sandbox for Google Apps Script: A Feasibility Study on Securely Executing Code Generated by Gemini CL](https://medium.com/google-cloud/a-fake-sandbox-for-google-apps-script-a-feasibility-study-on-securely-executing-code-generated-by-cc985ce5dae3)
|
|
186
|
+
- [A Fake-Sandbox for Google Apps Script: A Feasibility Study on Securely Executing Code Generated by Gemini CL](https://medium.com/google-cloud/a-fake-sandbox-for-google-apps-script-a-feasibility-study-on-securely-executing-code-generated-by-cc985ce5dae3)
|
package/gas-fakes.js
CHANGED
|
@@ -31,7 +31,7 @@ const VERSION = pjson.version;
|
|
|
31
31
|
// CONSTANTS & UTILITIES
|
|
32
32
|
// -----------------------------------------------------------------------------
|
|
33
33
|
|
|
34
|
-
const CLI_VERSION = "0.0.
|
|
34
|
+
const CLI_VERSION = "0.0.12";
|
|
35
35
|
const MCP_VERSION = "0.0.3";
|
|
36
36
|
const execAsync = promisify(exec);
|
|
37
37
|
|
|
@@ -529,6 +529,7 @@ async function main() {
|
|
|
529
529
|
.description(
|
|
530
530
|
"Initializes the configuration by creating or updating the .env file."
|
|
531
531
|
)
|
|
532
|
+
.option("-e, --env <path>", "Path to a custom .env file.")
|
|
532
533
|
.action(initializeConfiguration);
|
|
533
534
|
|
|
534
535
|
program
|
|
@@ -538,7 +539,22 @@ async function main() {
|
|
|
538
539
|
|
|
539
540
|
program
|
|
540
541
|
.command("enableAPIs")
|
|
541
|
-
.description(
|
|
542
|
+
.description(
|
|
543
|
+
"Enables or disables required Google Cloud APIs for the project."
|
|
544
|
+
)
|
|
545
|
+
.option("--all", "Enable all default Google Cloud APIs.")
|
|
546
|
+
.option("--edrive", "Enable drive.googleapis.com")
|
|
547
|
+
.option("--ddrive", "Disable drive.googleapis.com")
|
|
548
|
+
.option("--esheets", "Enable sheets.googleapis.com")
|
|
549
|
+
.option("--dsheets", "Disable sheets.googleapis.com")
|
|
550
|
+
.option("--eforms", "Enable forms.googleapis.com")
|
|
551
|
+
.option("--dforms", "Disable forms.googleapis.com")
|
|
552
|
+
.option("--edocs", "Enable docs.googleapis.com")
|
|
553
|
+
.option("--ddocs", "Disable docs.googleapis.com")
|
|
554
|
+
.option("--egmail", "Enable gmail.googleapis.com")
|
|
555
|
+
.option("--dgmail", "Disable gmail.googleapis.com")
|
|
556
|
+
.option("--elogging", "Enable logging.googleapis.com")
|
|
557
|
+
.option("--dlogging", "Disable logging.googleapis.com")
|
|
542
558
|
.action(enableGoogleAPIs);
|
|
543
559
|
|
|
544
560
|
// MCP server command
|
package/package.json
CHANGED
package/setup.js
CHANGED
|
@@ -2,13 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
import prompts from "prompts";
|
|
4
4
|
import dotenv from "dotenv";
|
|
5
|
-
import fs from "fs";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
6
7
|
import path from "path";
|
|
7
8
|
import os from "os";
|
|
8
9
|
import { execSync } from "child_process";
|
|
9
10
|
|
|
10
11
|
// --- Utility Functions ---
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Search .env file
|
|
15
|
+
* @param {string} dir - Start directory
|
|
16
|
+
* @returns {Promise<string[]>}
|
|
17
|
+
*/
|
|
18
|
+
async function findEnvFiles(dir) {
|
|
19
|
+
try {
|
|
20
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
21
|
+
const promises = entries.map((entry) => {
|
|
22
|
+
const fullPath = path.join(dir, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
if (entry.name === "node_modules") {
|
|
25
|
+
return Promise.resolve([]);
|
|
26
|
+
}
|
|
27
|
+
return findEnvFiles(fullPath);
|
|
28
|
+
} else if (entry.isFile() && entry.name === ".env") {
|
|
29
|
+
return Promise.resolve(fullPath);
|
|
30
|
+
}
|
|
31
|
+
return Promise.resolve([]);
|
|
32
|
+
});
|
|
33
|
+
const results = await Promise.all(promises);
|
|
34
|
+
return results.flat();
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error(`No directory: ${dir}`);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
12
41
|
/**
|
|
13
42
|
* Checks if the gcloud CLI is installed and available in the system's PATH.
|
|
14
43
|
* If not, it prints an informative message and exits the script.
|
|
@@ -46,104 +75,294 @@ function runCommand(command) {
|
|
|
46
75
|
}
|
|
47
76
|
|
|
48
77
|
// --- Exported Command Implementations ---
|
|
78
|
+
export async function initializeConfiguration(options = {}) {
|
|
79
|
+
let envPath;
|
|
49
80
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
81
|
+
if (options.env) {
|
|
82
|
+
envPath = path.resolve(process.cwd(), options.env);
|
|
83
|
+
console.log(`-> Using specified .env path: ${envPath}`);
|
|
84
|
+
} else {
|
|
85
|
+
const foundFiles = await findEnvFiles(process.cwd());
|
|
86
|
+
if (foundFiles.length > 0) {
|
|
87
|
+
const choices = foundFiles.map((file) => ({
|
|
88
|
+
title: file,
|
|
89
|
+
value: file,
|
|
90
|
+
}));
|
|
91
|
+
choices.push({
|
|
92
|
+
title: "Create a new .env file in the current directory",
|
|
93
|
+
value: "new",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const response = await prompts({
|
|
97
|
+
type: "select",
|
|
98
|
+
name: "envPathSelection",
|
|
99
|
+
message: "Found .env file(s). Which one would you like to use?",
|
|
100
|
+
choices: choices,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (typeof response.envPathSelection === "undefined") {
|
|
104
|
+
console.log("Initialization cancelled.");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (response.envPathSelection === "new") {
|
|
109
|
+
envPath = path.join(process.cwd(), ".env");
|
|
110
|
+
} else {
|
|
111
|
+
envPath = response.envPathSelection;
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
console.log(
|
|
115
|
+
"No .env file found. A new one will be created in the current directory."
|
|
116
|
+
);
|
|
117
|
+
envPath = path.join(process.cwd(), ".env");
|
|
118
|
+
}
|
|
119
|
+
console.log(`-> Using .env file at: ${envPath}`);
|
|
120
|
+
}
|
|
57
121
|
|
|
58
|
-
|
|
59
|
-
if (
|
|
122
|
+
let existingConfig = {};
|
|
123
|
+
if (existsSync(envPath)) {
|
|
60
124
|
console.log(
|
|
61
125
|
"Found existing .env file. Loading current values as defaults."
|
|
62
126
|
);
|
|
63
|
-
existingConfig = dotenv.
|
|
127
|
+
existingConfig = dotenv.parse(readFileSync(envPath));
|
|
64
128
|
}
|
|
65
129
|
|
|
66
130
|
console.log("--------------------------------------------------");
|
|
67
131
|
console.log("Configuring .env for gas-fakes");
|
|
68
132
|
console.log("Press Enter to accept the default value in brackets.");
|
|
133
|
+
console.log("Use Space to select/deselect scopes.");
|
|
69
134
|
console.log("--------------------------------------------------");
|
|
70
135
|
|
|
71
|
-
const
|
|
136
|
+
const existingExtraScopes = existingConfig.EXTRA_SCOPES
|
|
137
|
+
? existingConfig.EXTRA_SCOPES.split(",").filter((s) => s)
|
|
138
|
+
: [];
|
|
139
|
+
|
|
140
|
+
const responses = {};
|
|
141
|
+
|
|
142
|
+
// --- Stage 1: Basic Info ---
|
|
143
|
+
const basicInfoQuestions = [
|
|
72
144
|
{
|
|
73
145
|
type: "text",
|
|
74
146
|
name: "GCP_PROJECT_ID",
|
|
75
147
|
message: "Enter your GCP Project ID",
|
|
76
|
-
initial: existingConfig.GCP_PROJECT_ID ||
|
|
148
|
+
initial: existingConfig.GCP_PROJECT_ID || process.env.GOOGLE_CLOUD_PROJECT,
|
|
77
149
|
},
|
|
78
150
|
{
|
|
79
151
|
type: "text",
|
|
80
152
|
name: "DRIVE_TEST_FILE_ID",
|
|
81
|
-
message: "Enter a test Drive file ID for authentication checks",
|
|
153
|
+
message: "Enter a test Drive file ID for authentication checks (optional)",
|
|
82
154
|
initial: existingConfig.DRIVE_TEST_FILE_ID || "",
|
|
83
155
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
const basicInfoResponses = await prompts(basicInfoQuestions);
|
|
160
|
+
if (typeof basicInfoResponses.GCP_PROJECT_ID === "undefined") {
|
|
161
|
+
console.log("Initialization cancelled.");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
Object.assign(responses, basicInfoResponses);
|
|
165
|
+
|
|
166
|
+
// --- Stage 2: Scopes ---
|
|
167
|
+
|
|
168
|
+
const DEFAULT_SCOPES_VALUES = [
|
|
169
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
170
|
+
"openid",
|
|
171
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
172
|
+
];
|
|
173
|
+
console.log(
|
|
174
|
+
"\nThe following default scopes are required for basic operations and will be enabled automatically:"
|
|
175
|
+
);
|
|
176
|
+
DEFAULT_SCOPES_VALUES.forEach((scope) => console.log(` - ${scope}`));
|
|
177
|
+
responses.DEFAULT_SCOPES = DEFAULT_SCOPES_VALUES;
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
const extraScopeQuestion = {
|
|
181
|
+
type: "multiselect",
|
|
182
|
+
name: "EXTRA_SCOPES",
|
|
183
|
+
message: "Select any extra scopes your script requires",
|
|
184
|
+
|
|
185
|
+
// i think we only need to have drive (which we must have for all the others anyway)
|
|
186
|
+
choices: [
|
|
187
|
+
{
|
|
188
|
+
title: "Workspace resources",
|
|
189
|
+
value: "https://www.googleapis.com/auth/drive",
|
|
190
|
+
},
|
|
191
|
+
/*
|
|
192
|
+
{
|
|
193
|
+
title: "Sheets (full access)",
|
|
194
|
+
value: "https://www.googleapis.com/auth/spreadsheets",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
title: "Docs (full access)",
|
|
198
|
+
value: "https://www.googleapis.com/auth/documents",
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
title: "Forms (full access)",
|
|
202
|
+
value: "https://www.googleapis.com/auth/forms",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
title: "Gmail (send mail)",
|
|
206
|
+
value: "https://www.googleapis.com/auth/gmail.send",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
title: "Gmail (full access)",
|
|
210
|
+
value: "https://www.googleapis.com/auth/gmail.modify",
|
|
211
|
+
},
|
|
212
|
+
*/
|
|
213
|
+
{
|
|
214
|
+
// actually labels are not sensitive
|
|
215
|
+
title: "Gmail labels",
|
|
216
|
+
value: "https://www.googleapis.com/auth/gmail.labels",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
sensitivity: "sensitive",
|
|
220
|
+
title: "Gmail compose",
|
|
221
|
+
value: "https://www.googleapis.com/auth/gmail.compose",
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
].map((scope) => ({
|
|
225
|
+
...scope,
|
|
226
|
+
title: scope.sensitivity ? `[${scope.sensitivity}] ${scope.title}` : scope.title,
|
|
227
|
+
// because we always need drive for ant extra scopes
|
|
228
|
+
selected:
|
|
229
|
+
existingExtraScopes.length > 0
|
|
230
|
+
? existingExtraScopes.includes(scope.value)
|
|
231
|
+
: scope.value === "https://www.googleapis.com/auth/drive",
|
|
232
|
+
})),
|
|
233
|
+
hint: "- Use space to select/deselect. Press Enter to submit.",
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// to check for any kind of sensitivity
|
|
237
|
+
const sensitiveScopesList = extraScopeQuestion.choices.filter(
|
|
238
|
+
(scope) => scope.sensitivity
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const extraScopeResponses = await prompts(extraScopeQuestion);
|
|
242
|
+
|
|
243
|
+
if (typeof extraScopeResponses.EXTRA_SCOPES === "undefined") {
|
|
244
|
+
console.log("Initialization cancelled.");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
Object.assign(responses, extraScopeResponses);
|
|
248
|
+
|
|
249
|
+
const selectedExtraScopes = responses.EXTRA_SCOPES || [];
|
|
250
|
+
|
|
251
|
+
const usesSensitiveScopes = sensitiveScopesList.some((s) =>
|
|
252
|
+
selectedExtraScopes.includes(s.value)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if (usesSensitiveScopes) {
|
|
257
|
+
console.log("\n--------------------------------------------------");
|
|
258
|
+
console.log("You have selected sensitive or restricted scopes. Google requires an OAuth client credential file for these.");
|
|
259
|
+
console.log('See the getting started guide https://github.com/brucemcpherson/gas-fakes/blob/main/GETTING_STARTED.md for how.')
|
|
260
|
+
console.log("--------------------------------------------------");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
const clientCredentialQuestion = {
|
|
265
|
+
type: "text",
|
|
266
|
+
name: "CLIENT_CREDENTIAL_FILE",
|
|
267
|
+
message: usesSensitiveScopes
|
|
268
|
+
? "Enter the path and filename for your OAuth client credentials JSON"
|
|
269
|
+
: "Enter path to OAuth client credentials JSON (optional)",
|
|
270
|
+
initial: existingConfig.CLIENT_CREDENTIAL_FILE || "",
|
|
271
|
+
validate: (input) => {
|
|
272
|
+
const trimmedInput = input.trim();
|
|
273
|
+
|
|
274
|
+
if (usesSensitiveScopes) {
|
|
275
|
+
if (trimmedInput === "") {
|
|
276
|
+
return "This field is required for the selected sensitive scopes.";
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
if (trimmedInput === "") {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const resolvedPath = path.resolve(process.cwd(), trimmedInput);
|
|
285
|
+
if (!existsSync(resolvedPath)) {
|
|
286
|
+
return `File not found at '${resolvedPath}'. Please check the path and try again.`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return true;
|
|
105
290
|
},
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const clientCredentialResponse = await prompts(clientCredentialQuestion);
|
|
294
|
+
if (typeof clientCredentialResponse.CLIENT_CREDENTIAL_FILE === "undefined") {
|
|
295
|
+
console.log("Initialization cancelled.");
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
Object.assign(responses, clientCredentialResponse);
|
|
299
|
+
|
|
300
|
+
// --- Stage 3: Remaining Config ---
|
|
301
|
+
const defaultScopesDisplay = `\n - Default: [${responses.DEFAULT_SCOPES.join(
|
|
302
|
+
", "
|
|
303
|
+
)}]`;
|
|
304
|
+
const extraScopesDisplay =
|
|
305
|
+
responses.EXTRA_SCOPES && responses.EXTRA_SCOPES.length > 0
|
|
306
|
+
? `\n - Extra: [${responses.EXTRA_SCOPES.join(", ")}]`
|
|
307
|
+
: "\n - Extra: [None]";
|
|
308
|
+
|
|
309
|
+
const remainingQuestions = [{
|
|
310
|
+
type: "toggle",
|
|
311
|
+
name: "QUIET",
|
|
312
|
+
message: "Run gas-fakes package in quiet mode",
|
|
313
|
+
initial: existingConfig.QUIET === "true" ? true : false,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
type: "select",
|
|
317
|
+
name: "LOG_DESTINATION",
|
|
318
|
+
message: `Selected Scopes:${defaultScopesDisplay}${extraScopesDisplay}\n\nEnter logging destination`,
|
|
319
|
+
choices: [
|
|
320
|
+
{ title: "CONSOLE", value: "CONSOLE" },
|
|
321
|
+
{ title: "CLOUD", value: "CLOUD" },
|
|
322
|
+
{ title: "BOTH", value: "BOTH" },
|
|
323
|
+
{ title: "NONE", value: "NONE" },
|
|
324
|
+
],
|
|
325
|
+
initial:
|
|
326
|
+
["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
|
|
327
|
+
existingConfig.LOG_DESTINATION
|
|
328
|
+
) > -1
|
|
329
|
+
? ["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
|
|
118
330
|
existingConfig.LOG_DESTINATION
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
],
|
|
133
|
-
initial:
|
|
134
|
-
["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE?.toUpperCase()) >
|
|
331
|
+
)
|
|
332
|
+
: 0,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
type: "select",
|
|
336
|
+
name: "STORE_TYPE",
|
|
337
|
+
message: "Enter storage type",
|
|
338
|
+
choices: [
|
|
339
|
+
{ title: "FILE", value: "FILE" },
|
|
340
|
+
{ title: "UPSTASH", value: "UPSTASH" },
|
|
341
|
+
],
|
|
342
|
+
initial:
|
|
343
|
+
["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE?.toUpperCase()) >
|
|
135
344
|
-1
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
345
|
+
? ["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE.toUpperCase())
|
|
346
|
+
: 0,
|
|
347
|
+
}
|
|
139
348
|
];
|
|
140
349
|
|
|
141
|
-
const
|
|
350
|
+
const remainingResponses = await prompts(remainingQuestions);
|
|
351
|
+
if (typeof remainingResponses.LOG_DESTINATION === "undefined") {
|
|
352
|
+
console.log("Initialization cancelled.");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
Object.assign(responses, remainingResponses);
|
|
356
|
+
|
|
357
|
+
// Convert scope arrays to comma-separated strings for saving
|
|
358
|
+
if (Array.isArray(responses.DEFAULT_SCOPES)) {
|
|
359
|
+
responses.DEFAULT_SCOPES = responses.DEFAULT_SCOPES.join(",");
|
|
360
|
+
}
|
|
361
|
+
if (Array.isArray(responses.EXTRA_SCOPES)) {
|
|
362
|
+
responses.EXTRA_SCOPES = responses.EXTRA_SCOPES.join(",");
|
|
363
|
+
}
|
|
142
364
|
|
|
143
365
|
if (responses.STORE_TYPE === "UPSTASH") {
|
|
144
|
-
console.log(
|
|
145
|
-
"Upstash storage selected. Please provide your Redis credentials."
|
|
146
|
-
);
|
|
147
366
|
const upstashQuestions = [
|
|
148
367
|
{
|
|
149
368
|
type: "text",
|
|
@@ -159,50 +378,78 @@ export async function initializeConfiguration() {
|
|
|
159
378
|
},
|
|
160
379
|
];
|
|
161
380
|
const upstashResponses = await prompts(upstashQuestions);
|
|
381
|
+
|
|
382
|
+
if (typeof upstashResponses.UPSTASH_REDIS_REST_URL === "undefined") {
|
|
383
|
+
console.log("Initialization cancelled during Upstash configuration.");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
162
386
|
Object.assign(responses, upstashResponses);
|
|
163
387
|
}
|
|
164
388
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# Logging destination for Logger.log() ('CONSOLE', 'CLOUD', 'BOTH', 'NONE')
|
|
184
|
-
LOG_DESTINATION="${finalConfig.LOG_DESTINATION || "CONSOLE"}"
|
|
185
|
-
|
|
186
|
-
# Scopes for authentication
|
|
187
|
-
# these are the scopes set by default - take some of these out if you want to minimize access
|
|
188
|
-
DEFAULT_SCOPES="${finalConfig.DEFAULT_SCOPES || ""}"
|
|
189
|
-
EXTRA_SCOPES="${finalConfig.EXTRA_SCOPES || ""}"
|
|
190
|
-
`.trim();
|
|
191
|
-
|
|
192
|
-
if (
|
|
193
|
-
finalConfig.UPSTASH_REDIS_REST_URL &&
|
|
194
|
-
finalConfig.UPSTASH_REDIS_REST_TOKEN
|
|
195
|
-
) {
|
|
196
|
-
envContent += `
|
|
197
|
-
|
|
198
|
-
# Upstash credentials (only used if STORE_TYPE is 'UPSTASH')
|
|
199
|
-
UPSTASH_REDIS_REST_URL="${finalConfig.UPSTASH_REDIS_REST_URL}"
|
|
200
|
-
UPSTASH_REDIS_REST_TOKEN="${finalConfig.UPSTASH_REDIS_REST_TOKEN}"
|
|
201
|
-
`;
|
|
389
|
+
// --- Confirmation Step ---
|
|
390
|
+
console.log("\n------------------ Summary ------------------");
|
|
391
|
+
Object.entries(responses).forEach(([key, value]) => {
|
|
392
|
+
if (value !== undefined) console.log(`${key}: ${value}`);
|
|
393
|
+
});
|
|
394
|
+
console.log("-------------------------------------------");
|
|
395
|
+
|
|
396
|
+
const confirmSave = await prompts({
|
|
397
|
+
type: "confirm",
|
|
398
|
+
name: "save",
|
|
399
|
+
message: `Save this configuration to ${envPath}?`,
|
|
400
|
+
initial: true,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
if (!confirmSave.save) {
|
|
404
|
+
console.log("Configuration discarded. No changes were made.");
|
|
405
|
+
return;
|
|
202
406
|
}
|
|
203
407
|
|
|
204
|
-
|
|
408
|
+
// --- File Writing Logic ---
|
|
409
|
+
console.log(`Writing configuration to ${envPath}...`);
|
|
410
|
+
const inits = responses.STORE_TYPE !== "UPSTASH" ? { UPSTASH_REDIS_REST_TOKEN: "", UPSTASH_REDIS_REST_URL: "" } : {}
|
|
411
|
+
const finalConfig = { ...existingConfig, ...responses, ...inits };
|
|
412
|
+
|
|
413
|
+
const envContent = Reflect.ownKeys(finalConfig).map((key) => {
|
|
414
|
+
|
|
415
|
+
const item = finalConfig[key];
|
|
416
|
+
console.log (key, item)
|
|
417
|
+
return `${key}="${(item.toString() || "").trim()}"`;
|
|
418
|
+
}).join("\n")
|
|
419
|
+
/* replacing this to include existing values
|
|
420
|
+
let envContent = `
|
|
421
|
+
# Google Cloud Project ID (required)
|
|
422
|
+
GCP_PROJECT_ID="${finalConfig.GCP_PROJECT_ID || ""}"
|
|
423
|
+
|
|
424
|
+
# Path to OAuth client credentials for restricted scopes (optional)
|
|
425
|
+
CLIENT_CREDENTIAL_FILE="${finalConfig.CLIENT_CREDENTIAL_FILE || ""}"
|
|
426
|
+
|
|
427
|
+
# A test file ID for checking authentication (optional)
|
|
428
|
+
DRIVE_TEST_FILE_ID="${finalConfig.DRIVE_TEST_FILE_ID || ""}"
|
|
429
|
+
|
|
430
|
+
# Storage configuration for PropertiesService and CacheService ('FILE' or 'UPSTASH')
|
|
431
|
+
STORE_TYPE="${finalConfig.STORE_TYPE || "FILE"}"
|
|
432
|
+
|
|
433
|
+
# Logging destination for Logger.log() ('CONSOLE', 'CLOUD', 'BOTH', 'NONE')
|
|
434
|
+
LOG_DESTINATION="${finalConfig.LOG_DESTINATION || "CONSOLE"}"
|
|
435
|
+
|
|
436
|
+
# Scopes for authentication
|
|
437
|
+
DEFAULT_SCOPES="${finalConfig.DEFAULT_SCOPES || ""}"
|
|
438
|
+
EXTRA_SCOPES="${finalConfig.EXTRA_SCOPES || ""}"
|
|
439
|
+
`.trim();
|
|
440
|
+
|
|
441
|
+
if (finalConfig.STORE_TYPE === "UPSTASH") {
|
|
442
|
+
envContent += `
|
|
443
|
+
|
|
444
|
+
# Upstash credentials (only used if STORE_TYPE is 'UPSTASH')
|
|
445
|
+
UPSTASH_REDIS_REST_URL="${finalConfig.UPSTASH_REDIS_REST_URL || ""}"
|
|
446
|
+
UPSTASH_REDIS_REST_TOKEN="${finalConfig.UPSTASH_REDIS_REST_TOKEN || ""}"
|
|
447
|
+
`;
|
|
448
|
+
}
|
|
449
|
+
*/
|
|
450
|
+
writeFileSync(envPath, envContent + "\n", "utf8");
|
|
205
451
|
|
|
452
|
+
console.log("--------------------------------------------------");
|
|
206
453
|
console.log("Setup complete. Your .env file has been updated.");
|
|
207
454
|
console.log("--------------------------------------------------");
|
|
208
455
|
}
|
|
@@ -217,13 +464,13 @@ export function authenticateUser() {
|
|
|
217
464
|
const rootDirectory = process.cwd();
|
|
218
465
|
const envPath = path.join(rootDirectory, ".env");
|
|
219
466
|
|
|
220
|
-
if (!
|
|
467
|
+
if (!existsSync(envPath)) {
|
|
221
468
|
console.error(`Error: .env file not found at '${envPath}'`);
|
|
222
469
|
console.error("Please run './gas-fakes.js init' first.");
|
|
223
470
|
process.exit(1);
|
|
224
471
|
}
|
|
225
472
|
|
|
226
|
-
dotenv.config({ path: envPath });
|
|
473
|
+
dotenv.config({ path: envPath, quiet: true });
|
|
227
474
|
|
|
228
475
|
const {
|
|
229
476
|
GCP_PROJECT_ID,
|
|
@@ -263,7 +510,7 @@ export function authenticateUser() {
|
|
|
263
510
|
clientPath = path.join(rootDirectory, clientPath);
|
|
264
511
|
}
|
|
265
512
|
|
|
266
|
-
if (
|
|
513
|
+
if (existsSync(clientPath)) {
|
|
267
514
|
clientFlag = `--client-id-file="${clientPath}"`;
|
|
268
515
|
} else {
|
|
269
516
|
console.error(
|
|
@@ -335,8 +582,8 @@ export function authenticateUser() {
|
|
|
335
582
|
const activeConfigPath = path.join(gcloudConfigDir, "active_config");
|
|
336
583
|
|
|
337
584
|
let currentConfig = "unknown";
|
|
338
|
-
if (
|
|
339
|
-
currentConfig =
|
|
585
|
+
if (existsSync(activeConfigPath)) {
|
|
586
|
+
currentConfig = readFileSync(activeConfigPath, "utf8").trim();
|
|
340
587
|
} else {
|
|
341
588
|
console.warn(
|
|
342
589
|
`Warning: Could not find active_config file at ${activeConfigPath}`
|
|
@@ -373,22 +620,60 @@ export function authenticateUser() {
|
|
|
373
620
|
}
|
|
374
621
|
|
|
375
622
|
/**
|
|
376
|
-
* Handles the 'enableAPIs' command to enable necessary Google Cloud services.
|
|
623
|
+
* Handles the 'enableAPIs' command to enable or disable necessary Google Cloud services based on options.
|
|
624
|
+
* @param {object} options Options object provided by commander.js.
|
|
377
625
|
*/
|
|
378
|
-
export function enableGoogleAPIs() {
|
|
379
|
-
// First, check if gcloud CLI is available.
|
|
626
|
+
export function enableGoogleAPIs(options) {
|
|
380
627
|
checkForGcloudCli();
|
|
381
628
|
|
|
382
|
-
const
|
|
383
|
-
"drive.googleapis.com",
|
|
384
|
-
"sheets.googleapis.com",
|
|
385
|
-
"forms.googleapis.com",
|
|
386
|
-
"docs.googleapis.com",
|
|
387
|
-
"gmail.googleapis.com",
|
|
388
|
-
"logging.googleapis.com",
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
629
|
+
const API_SERVICES = {
|
|
630
|
+
drive: "drive.googleapis.com",
|
|
631
|
+
sheets: "sheets.googleapis.com",
|
|
632
|
+
forms: "forms.googleapis.com",
|
|
633
|
+
docs: "docs.googleapis.com",
|
|
634
|
+
gmail: "gmail.googleapis.com",
|
|
635
|
+
logging: "logging.googleapis.com",
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const servicesToEnable = new Set();
|
|
639
|
+
const servicesToDisable = new Set();
|
|
640
|
+
if (options.all || Object.keys(options).length === 0) {
|
|
641
|
+
Object.values(API_SERVICES).forEach((service) =>
|
|
642
|
+
servicesToEnable.add(service)
|
|
643
|
+
);
|
|
644
|
+
} else {
|
|
645
|
+
for (const key in API_SERVICES) {
|
|
646
|
+
if (options[`e${key}`]) {
|
|
647
|
+
servicesToEnable.add(API_SERVICES[key]);
|
|
648
|
+
}
|
|
649
|
+
if (options[`d${key}`]) {
|
|
650
|
+
servicesToDisable.add(API_SERVICES[key]);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (servicesToEnable.size > 0) {
|
|
655
|
+
const enableList = Array.from(servicesToEnable);
|
|
656
|
+
console.log(`Enabling Google Cloud services: ${enableList.join(", ")}...`);
|
|
657
|
+
runCommand(`gcloud services enable ${enableList.join(" ")}`);
|
|
658
|
+
console.log("Services enabled successfully.");
|
|
659
|
+
}
|
|
660
|
+
if (servicesToDisable.size > 0) {
|
|
661
|
+
const disableList = Array.from(servicesToDisable);
|
|
662
|
+
console.log(
|
|
663
|
+
`Disabling Google Cloud services: ${disableList.join(", ")}...`
|
|
664
|
+
);
|
|
665
|
+
runCommand(`gcloud services disable ${disableList.join(" ")}`);
|
|
666
|
+
console.log("Services disabled successfully.");
|
|
667
|
+
}
|
|
668
|
+
if (
|
|
669
|
+
servicesToEnable.size === 0 &&
|
|
670
|
+
servicesToDisable.size === 0 &&
|
|
671
|
+
Object.keys(options).length > 0 &&
|
|
672
|
+
!options.all
|
|
673
|
+
) {
|
|
674
|
+
console.log("No specific APIs were selected to enable or disable.");
|
|
675
|
+
console.log(
|
|
676
|
+
"Use '--all' to enable all default APIs, or specify flags like '--edrive' or '--ddrive'."
|
|
677
|
+
);
|
|
678
|
+
}
|
|
394
679
|
}
|
|
@@ -48,7 +48,6 @@ class FakeAdvDocuments extends FakeAdvResource {
|
|
|
48
48
|
|
|
49
49
|
// Invalidate the cache for this document since we are updating it.
|
|
50
50
|
docsCacher.clear(documentId);
|
|
51
|
-
// console.log (JSON.stringify(requests))
|
|
52
51
|
const { response, data } = this._call("batchUpdate", {
|
|
53
52
|
documentId,
|
|
54
53
|
requestBody: requests
|
|
@@ -8,13 +8,12 @@ export const lazyLoaderApp = (app, name, maker) => {
|
|
|
8
8
|
|
|
9
9
|
// if it hasne been intialized yet then do that
|
|
10
10
|
if (!app) {
|
|
11
|
-
//console.log ('...loading', name)
|
|
12
11
|
app = maker()
|
|
13
12
|
}
|
|
14
13
|
// this is the actual driveApp we'll return from the proxy
|
|
15
14
|
return app
|
|
16
15
|
}
|
|
17
|
-
|
|
16
|
+
|
|
18
17
|
Proxies.registerProxy(name, getApp)
|
|
19
18
|
|
|
20
19
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file Provides helper functions for working with fake document elements.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import {slogger } from "../../support/slogger.js";
|
|
6
6
|
import { Utils } from "../../support/utils.js";
|
|
7
7
|
const { is } = Utils
|
|
8
8
|
|
|
@@ -38,7 +38,7 @@ export const getElementProp = (se) => {
|
|
|
38
38
|
return { prop: 'body', type: 'BODY_SECTION' };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
slogger.error(se);
|
|
42
42
|
throw new Error('couldnt establish structural element type');
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -220,7 +220,7 @@ export const findItem = (elementMap, type, startIndex, segmentId) => {
|
|
|
220
220
|
return f.__type === type && f.startIndex === startIndex;
|
|
221
221
|
});
|
|
222
222
|
if (!item) {
|
|
223
|
-
|
|
223
|
+
slogger.error(elementMap.values())
|
|
224
224
|
throw new Error(`Couldnt find element ${type} at ${startIndex} in segment ${segmentId || 'body'}`)
|
|
225
225
|
}
|
|
226
226
|
return item
|
|
@@ -91,7 +91,7 @@ class ShadowDocument {
|
|
|
91
91
|
__twig: footnoteSectionTree,
|
|
92
92
|
};
|
|
93
93
|
this.__elementMap.set(footnoteSectionName, footnoteSectionElement);
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
|
|
96
96
|
// maps all the elements to their named range
|
|
97
97
|
const mapElements = (element, branch, segmentId, knownType = null) => {
|
|
@@ -127,7 +127,7 @@ class ShadowDocument {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
if (!is.integer(element.endIndex) || !is.integer(element.startIndex)) {
|
|
130
|
-
|
|
130
|
+
slogger.error(element);
|
|
131
131
|
throw new Error(`failed to find endindex/startindex for ${type}`);
|
|
132
132
|
}
|
|
133
133
|
// For an empty document, we use static, non-API names to avoid re-indexing issues.
|
|
@@ -13,7 +13,7 @@ import { isFolder, notYetImplemented, isFakeFolder, signatureArgs } from '../../
|
|
|
13
13
|
import { getParentsIterator } from './driveiterators.js';
|
|
14
14
|
import { improveFileCache } from "../../support/filecache.js"
|
|
15
15
|
import { getSharers } from '../../support/filesharers.js';
|
|
16
|
-
|
|
16
|
+
import {slogger } from "../../support/slogger.js";
|
|
17
17
|
/**
|
|
18
18
|
* basic fake File meta data
|
|
19
19
|
* these are shared between folders and files
|
|
@@ -34,7 +34,7 @@ export class FakeDriveMeta {
|
|
|
34
34
|
|
|
35
35
|
__preventRootDamage = (operation) => {
|
|
36
36
|
if (this.__isRoot) {
|
|
37
|
-
|
|
37
|
+
slogger.error (`Can't do ${operation} on root folder`)
|
|
38
38
|
throw new Error("Access denied: DriveApp")
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -189,7 +189,7 @@ const writeToCloudOrConsole = (message, loggerInstance) => {
|
|
|
189
189
|
headers,
|
|
190
190
|
})
|
|
191
191
|
if (response.getResponseCode() !== 200) {
|
|
192
|
-
console.
|
|
192
|
+
console.error ('logging failure', response.getContentText())
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
}
|
|
@@ -9,6 +9,7 @@ import { Auth } from '../../support/auth.js'
|
|
|
9
9
|
import { Proxies } from '../../support/proxies.js'
|
|
10
10
|
import { newFakeBehavior } from './behavior.js'
|
|
11
11
|
import { newCacheDropin , getUserIdFromToken} from '@mcpher/gas-flex-cache'
|
|
12
|
+
import {slogger } from "../../support/slogger.js";
|
|
12
13
|
/**
|
|
13
14
|
* fake ScriptApp.getOAuthToken
|
|
14
15
|
* @return {string} token
|
|
@@ -79,7 +80,7 @@ const checkScopesMatch = (required) => {
|
|
|
79
80
|
]
|
|
80
81
|
const hasIgnore = ignores.includes(s)
|
|
81
82
|
if (hasIgnore) {
|
|
82
|
-
|
|
83
|
+
slogger.warn('...ignoring requested scope for adc as google blocks it outside apps script' + s)
|
|
83
84
|
}
|
|
84
85
|
// if drive is authorized and drive.readonly is required that's okay too
|
|
85
86
|
// if drive.readonly is authorized and drive is requested thats not
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Proxies } from '../../support/proxies.js'
|
|
2
2
|
import { Utils } from '../../support/utils.js'
|
|
3
|
+
import {slogger } from "../../support/slogger.js";
|
|
3
4
|
|
|
4
5
|
const { is } = Utils
|
|
5
6
|
const checkArgs = (actual, expect = "boolean") => {
|
|
@@ -250,7 +251,7 @@ class FakeBehavior {
|
|
|
250
251
|
throw new Error(`Invalid sandbox id parameter (${id}) - must be a non-empty string`);
|
|
251
252
|
}
|
|
252
253
|
if (!this.isKnown(id)) {
|
|
253
|
-
|
|
254
|
+
slogger.log(`...adding file ${id} to sandbox allowed list`);
|
|
254
255
|
this.__createdIds.add(id);
|
|
255
256
|
}
|
|
256
257
|
}
|
|
@@ -344,7 +345,7 @@ class FakeBehavior {
|
|
|
344
345
|
trash() {
|
|
345
346
|
// this is where we would trash all the created files
|
|
346
347
|
if (!this.__cleanup) {
|
|
347
|
-
|
|
348
|
+
slogger.log('...skipping cleaning up sandbox files')
|
|
348
349
|
return [];
|
|
349
350
|
}
|
|
350
351
|
|
|
@@ -357,14 +358,14 @@ class FakeBehavior {
|
|
|
357
358
|
}
|
|
358
359
|
if (d) {
|
|
359
360
|
d.setTrashed(true);
|
|
360
|
-
|
|
361
|
+
slogger.log(`...trashed file ${d.getName()} (${id})`);
|
|
361
362
|
acc.push(id);
|
|
362
363
|
}
|
|
363
364
|
return acc;
|
|
364
365
|
}, []);
|
|
365
366
|
|
|
366
367
|
this.__createdIds.clear();
|
|
367
|
-
|
|
368
|
+
slogger.log(`...trashed ${trashed.length} sandboxed files`);
|
|
368
369
|
return trashed;
|
|
369
370
|
}
|
|
370
371
|
isKnown(id) {
|
|
@@ -15,10 +15,8 @@ import { newFakeNamedRange } from "./fakenamedrange.js";
|
|
|
15
15
|
import { newFakeProtection } from "./fakeprotection.js";
|
|
16
16
|
import { newFakeOverGridImage } from "./fakeovergridimage.js";
|
|
17
17
|
|
|
18
|
-
import { Syncit } from "../../support/syncit.js";
|
|
19
18
|
import { XMLParser } from "fast-xml-parser";
|
|
20
|
-
import {
|
|
21
|
-
|
|
19
|
+
import {slogger } from "../../support/slogger.js";
|
|
22
20
|
const { is, isEnum } = Utils;
|
|
23
21
|
|
|
24
22
|
export const newFakeSheet = (properties, parent) => {
|
|
@@ -1082,7 +1080,7 @@ export class FakeSheet {
|
|
|
1082
1080
|
const p = parser.parse(b.getDataAsString());
|
|
1083
1081
|
o[filename] = p;
|
|
1084
1082
|
} catch (err) {
|
|
1085
|
-
|
|
1083
|
+
slogger.error(err)
|
|
1086
1084
|
}
|
|
1087
1085
|
return o;
|
|
1088
1086
|
}, {});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Utils } from '../../support/utils.js'
|
|
2
|
-
|
|
2
|
+
import {slogger } from "../../support/slogger.js";
|
|
3
3
|
const { is, hexToRgb } = Utils
|
|
4
4
|
|
|
5
5
|
export const bandingThemeMap = {
|
|
@@ -135,7 +135,7 @@ export const isThemeColor = (color) =>{
|
|
|
135
135
|
// Make a gridrange from a range
|
|
136
136
|
export const makeGridRange = (range) => {
|
|
137
137
|
if (!isRange(range)) {
|
|
138
|
-
|
|
138
|
+
slogger.error(range)
|
|
139
139
|
throw new Error(`expected a range but got ${typeof range}`)
|
|
140
140
|
}
|
|
141
141
|
return {
|
|
@@ -5,9 +5,8 @@ import { Utils } from '../../support/utils.js'
|
|
|
5
5
|
import { storeModels } from './gasflex.js'
|
|
6
6
|
import { newCacheDropin } from '@mcpher/gas-flex-cache'
|
|
7
7
|
import { notYetImplemented } from '../../support/helpers.js'
|
|
8
|
-
import { file } from 'googleapis/build/src/apis/file/index.js'
|
|
9
8
|
const { is } = Utils
|
|
10
|
-
|
|
9
|
+
import {slogger } from "../../support/slogger.js";
|
|
11
10
|
/**
|
|
12
11
|
* what these props mean
|
|
13
12
|
* store_type = currently upstash or file - it defines the back end and maps to env variable
|
|
@@ -185,7 +184,7 @@ class FakeCacheService {
|
|
|
185
184
|
export const newFakeService = (kind) => {
|
|
186
185
|
kind = validateProp (kind, ServiceKind, 'service_kind')
|
|
187
186
|
const w = whichCache ()
|
|
188
|
-
|
|
187
|
+
slogger.log (`...${kind} store service is using store type ${w.type} as backend`)
|
|
189
188
|
return Proxies.guard(kind === ServiceKind.CACHE ? new FakeCacheService(w.type) : new FakePropertiesService(w.type))
|
|
190
189
|
}
|
|
191
190
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* const MyEnum = newFakeGasEnum('MyEnum', ['KEY_A', 'KEY_B']);
|
|
14
|
-
*
|
|
14
|
+
* c.log(MyEnum.KEY_A); // Outputs: "KEY_A"
|
|
15
15
|
* MyEnum.KEY_A = 'new value'; // Throws an error in strict mode
|
|
16
16
|
*
|
|
17
17
|
* @param {string} name The name of the enum, used for `toString()` representation (e.g., "ElementType").
|
package/src/support/helpers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {Utils} from './utils.js'
|
|
2
2
|
const {is, capital} = Utils
|
|
3
3
|
import { Proxies } from './proxies.js'
|
|
4
|
-
|
|
4
|
+
import {slogger } from "./slogger.js";
|
|
5
5
|
/**
|
|
6
6
|
* @constant
|
|
7
7
|
* @type {string}
|
|
@@ -142,7 +142,7 @@ export const signatureArgs = (received, method, objectType = 'Object') => {
|
|
|
142
142
|
try {
|
|
143
143
|
passedTypes = args.map(f=>is.null(f) ? 'null' : is(f)).map(f=>f==='null'? f :capital(f)).map(f=>f==='Object' ? objectType : f)
|
|
144
144
|
} catch (err) {
|
|
145
|
-
|
|
145
|
+
slogger.warn ("...warning failed signature check- probably an unsupported probe. probe of an enum - ignoring", args)
|
|
146
146
|
passedTypes=[]
|
|
147
147
|
}
|
|
148
148
|
const matchThrow = (mess = method) => {
|
|
@@ -171,7 +171,7 @@ export const advClassMaker = (props) => {
|
|
|
171
171
|
capped.map((f, i) => {
|
|
172
172
|
|
|
173
173
|
if (done.has(f)) {
|
|
174
|
-
|
|
174
|
+
slogger.warn('....WARNING duplicate property in advClassMaker', f)
|
|
175
175
|
}
|
|
176
176
|
done.add(f)
|
|
177
177
|
ob['get' + f] = () => ob[props[i]]
|
package/src/support/proxies.js
CHANGED
|
@@ -74,7 +74,7 @@ const registerProxy = (name, getApp) => {
|
|
|
74
74
|
serviceRegistry.add(name);
|
|
75
75
|
const value = new Proxy({}, getAppHandler(getApp, name))
|
|
76
76
|
// add it to the global space to mimic what apps script does
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
Object.defineProperty(globalThis, name, {
|
|
79
79
|
value,
|
|
80
80
|
enumerable: true,
|
|
@@ -103,7 +103,7 @@ const validateProperties = () => {
|
|
|
103
103
|
if (
|
|
104
104
|
// skip any inserted symbos
|
|
105
105
|
typeof prop !== 'symbol' &&
|
|
106
|
-
// sometimes typeof &
|
|
106
|
+
// sometimes typeof & c.log looks for ths
|
|
107
107
|
prop !== 'inspect' &&
|
|
108
108
|
// this is a mysterious property that APPS script sometimes checks for
|
|
109
109
|
prop !== '__GS_INTERNAL_isProxy' &&
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview a logger that can be silenced by an env variable
|
|
3
|
+
* used for all internal gas-fakes logging
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const q = process.env.QUIET || false
|
|
7
|
+
export const isQuiet = q.toString().toLowerCase()=== "true";
|
|
8
|
+
|
|
9
|
+
export const slogger = {
|
|
10
|
+
log: isQuiet ? () => {} : console.log,
|
|
11
|
+
error: console.error,
|
|
12
|
+
warn: console.warn,
|
|
13
|
+
info: isQuiet ? () => {} : console.info
|
|
14
|
+
};
|
package/src/support/utils.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import is from '@sindresorhus/is';
|
|
4
4
|
import { assert } from '@sindresorhus/is'
|
|
5
|
-
|
|
5
|
+
import {slogger} from './slogger.js'
|
|
6
6
|
|
|
7
7
|
const isNU = (item) => is.null(item) || is.undefined(item)
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ const fromJson = (text, failOnError = false) => {
|
|
|
14
14
|
try {
|
|
15
15
|
return JSON.parse(text)
|
|
16
16
|
} catch (err) {
|
|
17
|
-
|
|
17
|
+
slogger.warn(text)
|
|
18
18
|
if (failOnError) {
|
|
19
19
|
throw err
|
|
20
20
|
}
|
|
@@ -425,7 +425,7 @@ function stabilizeKeyOrder(obj) {
|
|
|
425
425
|
const lobify = (ob, mess = '') => {
|
|
426
426
|
let lob = ob
|
|
427
427
|
if (is.object(lob)) lob = stringCircular(lob)
|
|
428
|
-
|
|
428
|
+
slogger.log(mess, lob)
|
|
429
429
|
return ob
|
|
430
430
|
}
|
|
431
431
|
export const Utils = {
|
|
@@ -2,6 +2,7 @@ import { Worker } from 'worker_threads';
|
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
+
import { slogger} from '../slogger.js'
|
|
5
6
|
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = path.dirname(__filename);
|
|
@@ -92,7 +93,7 @@ worker.unref();
|
|
|
92
93
|
* whether normally or via signals like Ctrl+C.
|
|
93
94
|
*/
|
|
94
95
|
function cleanup() {
|
|
95
|
-
|
|
96
|
+
slogger.log('...terminating worker thread');
|
|
96
97
|
worker.terminate();
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
|
|
2
|
+
import { isQuiet } from '../slogger.js';
|
|
3
3
|
export const syncLog = (message) => {
|
|
4
|
-
fs.writeSync(1, `[Worker] ${message}\n`);
|
|
4
|
+
if (!isQuiet) fs.writeSync(1, `[Worker] ${message}\n`);
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
export const syncWarn = (message) => {
|
|
8
8
|
fs.writeSync(1, `[Worker Warn] ${message}\n`);
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
export const syncInfo = (message) => {
|
|
12
|
+
if (!isQuiet) fs.writeSync(1, `[Worker Info] ${message}\n`);
|
|
13
|
+
};
|
|
11
14
|
export const syncError = (message, error) => {
|
|
12
15
|
// Providing the error object is optional
|
|
13
16
|
const errorMessage = error ? `: ${error?.stack || error}` : '';
|