@mcpher/gas-fakes 1.2.15 → 1.2.17
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/gas-fakes.js +39 -10
- package/package.json +3 -2
- package/setup.js +394 -0
- package/src/services/spreadsheetapp/fakesheet.js +93 -22
- package/src/support/sxxlsx.js +39 -39
- package/src/support/syncit.js +2 -3
package/gas-fakes.js
CHANGED
|
@@ -14,6 +14,13 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
14
14
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
|
|
17
|
+
// --- Import setup commands ---
|
|
18
|
+
import {
|
|
19
|
+
initializeConfiguration,
|
|
20
|
+
authenticateUser,
|
|
21
|
+
enableGoogleAPIs,
|
|
22
|
+
} from "./setup.js";
|
|
23
|
+
|
|
17
24
|
// sync the version with gas fakes code since they share a package.json
|
|
18
25
|
import { createRequire } from "node:module";
|
|
19
26
|
const require = createRequire(import.meta.url);
|
|
@@ -24,7 +31,7 @@ const VERSION = pjson.version;
|
|
|
24
31
|
// CONSTANTS & UTILITIES
|
|
25
32
|
// -----------------------------------------------------------------------------
|
|
26
33
|
|
|
27
|
-
const CLI_VERSION = "0.0.
|
|
34
|
+
const CLI_VERSION = "0.0.10";
|
|
28
35
|
const MCP_VERSION = "0.0.3";
|
|
29
36
|
const execAsync = promisify(exec);
|
|
30
37
|
|
|
@@ -420,6 +427,7 @@ async function main() {
|
|
|
420
427
|
.description("A CLI tool to execute Google Apps Script with fakes/mocks.")
|
|
421
428
|
.version(VERSION, "-v, --version", "Display the current version");
|
|
422
429
|
|
|
430
|
+
// Default command to execute a script
|
|
423
431
|
program
|
|
424
432
|
.description("Execute a Google Apps Script file or string.")
|
|
425
433
|
.option("-f, --filename <string>", "Path to the Google Apps Script file.")
|
|
@@ -461,17 +469,19 @@ async function main() {
|
|
|
461
469
|
null
|
|
462
470
|
)
|
|
463
471
|
.action(async (options) => {
|
|
464
|
-
if (Object.keys(options).length === 0) {
|
|
465
|
-
program.help();
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
472
|
const { filename, script, env, gfsettings } = options;
|
|
470
473
|
if (!filename && !script) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
);
|
|
474
|
-
process.
|
|
474
|
+
// This action is for the default command. If a known command is passed (like 'init'), this won't run.
|
|
475
|
+
// We check if the command is not one of the others.
|
|
476
|
+
const knownCommands = program.commands.map((cmd) => cmd.name());
|
|
477
|
+
if (!process.argv.slice(2).some((arg) => knownCommands.includes(arg))) {
|
|
478
|
+
console.error(
|
|
479
|
+
"Error: You must provide a script via --filename or --script, or use a specific command (e.g., init, auth, mcp)."
|
|
480
|
+
);
|
|
481
|
+
program.help();
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
return;
|
|
475
485
|
}
|
|
476
486
|
|
|
477
487
|
// Load environment variables
|
|
@@ -513,6 +523,25 @@ async function main() {
|
|
|
513
523
|
});
|
|
514
524
|
});
|
|
515
525
|
|
|
526
|
+
// --- Setup commands ---
|
|
527
|
+
program
|
|
528
|
+
.command("init")
|
|
529
|
+
.description(
|
|
530
|
+
"Initializes the configuration by creating or updating the .env file."
|
|
531
|
+
)
|
|
532
|
+
.action(initializeConfiguration);
|
|
533
|
+
|
|
534
|
+
program
|
|
535
|
+
.command("auth")
|
|
536
|
+
.description("Runs the Google Cloud authentication and authorization flow.")
|
|
537
|
+
.action(authenticateUser);
|
|
538
|
+
|
|
539
|
+
program
|
|
540
|
+
.command("enableAPIs")
|
|
541
|
+
.description("Enables the required Google Cloud APIs for the project.")
|
|
542
|
+
.action(enableGoogleAPIs);
|
|
543
|
+
|
|
544
|
+
// MCP server command
|
|
516
545
|
program
|
|
517
546
|
.command("mcp")
|
|
518
547
|
.description("Launch gas-fakes as an MCP server.")
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"archiver": "^7.0.1",
|
|
11
11
|
"commander": "^14.0.1",
|
|
12
12
|
"dotenv": "^17.2.3",
|
|
13
|
-
"
|
|
13
|
+
"fast-xml-parser": "^5.3.0",
|
|
14
14
|
"get-stream": "^9.0.1",
|
|
15
15
|
"googleapis": "^161.0.0",
|
|
16
16
|
"got": "^14.4.7",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"keyv": "^5.5.0",
|
|
19
19
|
"keyv-file": "^5.1.3",
|
|
20
20
|
"mime": "^4.0.7",
|
|
21
|
+
"prompts": "^2.4.2",
|
|
21
22
|
"sleep-synchronously": "^2.0.0",
|
|
22
23
|
"unzipper": "^0.12.3",
|
|
23
24
|
"zod": "^3.25.76"
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
},
|
|
32
33
|
"name": "@mcpher/gas-fakes",
|
|
33
34
|
"author": "bruce mcpherson",
|
|
34
|
-
"version": "1.2.
|
|
35
|
+
"version": "1.2.17",
|
|
35
36
|
"license": "MIT",
|
|
36
37
|
"main": "main.js",
|
|
37
38
|
"description": "A proof of concept implementation of Apps Script Environment on Node",
|
package/setup.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
// setup.js: Setup for gas-fakes.
|
|
2
|
+
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
|
|
10
|
+
// --- Utility Functions ---
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if the gcloud CLI is installed and available in the system's PATH.
|
|
14
|
+
* If not, it prints an informative message and exits the script.
|
|
15
|
+
*/
|
|
16
|
+
function checkForGcloudCli() {
|
|
17
|
+
try {
|
|
18
|
+
// Execute a simple, non-destructive command to check if gcloud exists.
|
|
19
|
+
// The output is ignored to keep the console clean on success.
|
|
20
|
+
execSync("gcloud --version", { stdio: "ignore" });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
// The command failed, likely because gcloud is not installed or not in the PATH.
|
|
23
|
+
console.error("\n[Error] Google Cloud SDK (gcloud CLI) not found.");
|
|
24
|
+
console.error(
|
|
25
|
+
"This script requires the gcloud CLI to manage authentication and Google Cloud services."
|
|
26
|
+
);
|
|
27
|
+
console.error("Please install it by following the official instructions:");
|
|
28
|
+
console.error("https://cloud.google.com/sdk/gcloud");
|
|
29
|
+
process.exit(1); // Exit the script with an error code.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Helper function to run a shell command and print its output.
|
|
35
|
+
* @param {string} command The command to execute.
|
|
36
|
+
*/
|
|
37
|
+
function runCommand(command) {
|
|
38
|
+
try {
|
|
39
|
+
// Execute the command, inheriting stdio to show output/errors in real-time.
|
|
40
|
+
execSync(command, { stdio: "inherit" });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(`\nError executing command: ${command}`);
|
|
43
|
+
// The error message from the command is already shown due to 'inherit' stdio.
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- Exported Command Implementations ---
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Handles the 'init' command to configure the .env file.
|
|
52
|
+
*/
|
|
53
|
+
export async function initializeConfiguration() {
|
|
54
|
+
// Define the path to the .env file in the current working directory.
|
|
55
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
56
|
+
let existingConfig = {};
|
|
57
|
+
|
|
58
|
+
// --- Load existing values from .env file if it exists ---
|
|
59
|
+
if (fs.existsSync(envPath)) {
|
|
60
|
+
console.log(
|
|
61
|
+
"Found existing .env file. Loading current values as defaults."
|
|
62
|
+
);
|
|
63
|
+
existingConfig = dotenv.config({ path: envPath , quiet: true }).parsed || {};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log("--------------------------------------------------");
|
|
67
|
+
console.log("Configuring .env for gas-fakes");
|
|
68
|
+
console.log("Press Enter to accept the default value in brackets.");
|
|
69
|
+
console.log("--------------------------------------------------");
|
|
70
|
+
|
|
71
|
+
const questions = [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
name: "GCP_PROJECT_ID",
|
|
75
|
+
message: "Enter your GCP Project ID",
|
|
76
|
+
initial: existingConfig.GCP_PROJECT_ID || "",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
name: "DRIVE_TEST_FILE_ID",
|
|
81
|
+
message: "Enter a test Drive file ID for authentication checks",
|
|
82
|
+
initial: existingConfig.DRIVE_TEST_FILE_ID || "",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
name: "CLIENT_CREDENTIAL_FILE",
|
|
87
|
+
message: "Enter path to OAuth client credentials JSON (optional)",
|
|
88
|
+
initial: existingConfig.CLIENT_CREDENTIAL_FILE || "",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: "text",
|
|
92
|
+
name: "DEFAULT_SCOPES",
|
|
93
|
+
message: "Enter default scopes",
|
|
94
|
+
initial:
|
|
95
|
+
existingConfig.DEFAULT_SCOPES ||
|
|
96
|
+
"https://www.googleapis.com/auth/userinfo.email,openid,https://www.googleapis.com/auth/cloud-platform",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
name: "EXTRA_SCOPES",
|
|
101
|
+
message: "Enter any extra scopes (comma-separated)",
|
|
102
|
+
initial:
|
|
103
|
+
existingConfig.EXTRA_SCOPES ||
|
|
104
|
+
",https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/spreadsheets",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: "select",
|
|
108
|
+
name: "LOG_DESTINATION",
|
|
109
|
+
message: "Enter logging destination",
|
|
110
|
+
choices: [
|
|
111
|
+
{ title: "CONSOLE", value: "CONSOLE" },
|
|
112
|
+
{ title: "CLOUD", value: "CLOUD" },
|
|
113
|
+
{ title: "BOTH", value: "BOTH" },
|
|
114
|
+
{ title: "NONE", value: "NONE" },
|
|
115
|
+
],
|
|
116
|
+
initial:
|
|
117
|
+
["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
|
|
118
|
+
existingConfig.LOG_DESTINATION
|
|
119
|
+
) > -1
|
|
120
|
+
? ["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
|
|
121
|
+
existingConfig.LOG_DESTINATION
|
|
122
|
+
)
|
|
123
|
+
: 0,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: "select",
|
|
127
|
+
name: "STORE_TYPE",
|
|
128
|
+
message: "Enter storage type",
|
|
129
|
+
choices: [
|
|
130
|
+
{ title: "FILE", value: "FILE" },
|
|
131
|
+
{ title: "UPSTASH", value: "UPSTASH" },
|
|
132
|
+
],
|
|
133
|
+
initial:
|
|
134
|
+
["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE?.toUpperCase()) >
|
|
135
|
+
-1
|
|
136
|
+
? ["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE.toUpperCase())
|
|
137
|
+
: 0,
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const responses = await prompts(questions);
|
|
142
|
+
|
|
143
|
+
if (responses.STORE_TYPE === "UPSTASH") {
|
|
144
|
+
console.log(
|
|
145
|
+
"Upstash storage selected. Please provide your Redis credentials."
|
|
146
|
+
);
|
|
147
|
+
const upstashQuestions = [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
name: "UPSTASH_REDIS_REST_URL",
|
|
151
|
+
message: "Enter your Upstash Redis REST URL",
|
|
152
|
+
initial: existingConfig.UPSTASH_REDIS_REST_URL || "",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: "text",
|
|
156
|
+
name: "UPSTASH_REDIS_REST_TOKEN",
|
|
157
|
+
message: "Enter your Upstash Redis REST Token",
|
|
158
|
+
initial: existingConfig.UPSTASH_REDIS_REST_TOKEN || "",
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
const upstashResponses = await prompts(upstashQuestions);
|
|
162
|
+
Object.assign(responses, upstashResponses);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const finalConfig = { ...existingConfig, ...responses };
|
|
166
|
+
|
|
167
|
+
console.log("--------------------------------------------------");
|
|
168
|
+
console.log(`Writing configuration to ${envPath}...`);
|
|
169
|
+
|
|
170
|
+
let envContent = `
|
|
171
|
+
# Google Cloud Project ID (required)
|
|
172
|
+
GCP_PROJECT_ID="${finalConfig.GCP_PROJECT_ID || ""}"
|
|
173
|
+
|
|
174
|
+
# Path to OAuth client credentials for restricted scopes (optional)
|
|
175
|
+
CLIENT_CREDENTIAL_FILE="${finalConfig.CLIENT_CREDENTIAL_FILE || ""}"
|
|
176
|
+
|
|
177
|
+
# A test file ID for checking authentication (optional)
|
|
178
|
+
DRIVE_TEST_FILE_ID="${finalConfig.DRIVE_TEST_FILE_ID || ""}"
|
|
179
|
+
|
|
180
|
+
# Storage configuration for PropertiesService and CacheService ('FILE' or 'UPSTASH')
|
|
181
|
+
STORE_TYPE="${finalConfig.STORE_TYPE || "FILE"}"
|
|
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
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fs.writeFileSync(envPath, envContent);
|
|
205
|
+
|
|
206
|
+
console.log("Setup complete. Your .env file has been updated.");
|
|
207
|
+
console.log("--------------------------------------------------");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Handles the 'auth' command to authenticate with Google Cloud.
|
|
212
|
+
*/
|
|
213
|
+
export function authenticateUser() {
|
|
214
|
+
// First, check if gcloud CLI is available.
|
|
215
|
+
checkForGcloudCli();
|
|
216
|
+
|
|
217
|
+
const rootDirectory = process.cwd();
|
|
218
|
+
const envPath = path.join(rootDirectory, ".env");
|
|
219
|
+
|
|
220
|
+
if (!fs.existsSync(envPath)) {
|
|
221
|
+
console.error(`Error: .env file not found at '${envPath}'`);
|
|
222
|
+
console.error("Please run './gas-fakes.js init' first.");
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
dotenv.config({ path: envPath });
|
|
227
|
+
|
|
228
|
+
const {
|
|
229
|
+
GCP_PROJECT_ID,
|
|
230
|
+
DEFAULT_SCOPES,
|
|
231
|
+
EXTRA_SCOPES,
|
|
232
|
+
CLIENT_CREDENTIAL_FILE,
|
|
233
|
+
AC,
|
|
234
|
+
} = process.env;
|
|
235
|
+
|
|
236
|
+
if (!GCP_PROJECT_ID) {
|
|
237
|
+
console.error("Error: GCP_PROJECT_ID is not set in your .env file.");
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const defaultScopes =
|
|
242
|
+
DEFAULT_SCOPES ||
|
|
243
|
+
"https://www.googleapis.com/auth/userinfo.email,openid,https://www.googleapis.com/auth/cloud-platform";
|
|
244
|
+
const extraScopes =
|
|
245
|
+
EXTRA_SCOPES ||
|
|
246
|
+
"https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/spreadsheets";
|
|
247
|
+
|
|
248
|
+
let scopes = defaultScopes;
|
|
249
|
+
if (extraScopes && extraScopes.length > 0) {
|
|
250
|
+
scopes += (extraScopes.startsWith(",") ? "" : ",") + extraScopes;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const driveAccessFlag = "--enable-gdrive-access";
|
|
254
|
+
|
|
255
|
+
console.log(`...requesting scopes ${scopes}`);
|
|
256
|
+
|
|
257
|
+
let clientFlag = "";
|
|
258
|
+
if (CLIENT_CREDENTIAL_FILE) {
|
|
259
|
+
console.log("...attempting to use enhanced client credentials");
|
|
260
|
+
|
|
261
|
+
let clientPath = CLIENT_CREDENTIAL_FILE;
|
|
262
|
+
if (!path.isAbsolute(clientPath)) {
|
|
263
|
+
clientPath = path.join(rootDirectory, clientPath);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (fs.existsSync(clientPath)) {
|
|
267
|
+
clientFlag = `--client-id-file="${clientPath}"`;
|
|
268
|
+
} else {
|
|
269
|
+
console.error(
|
|
270
|
+
`Error: Client credential file specified in .env not found at '${clientPath}'`
|
|
271
|
+
);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
console.log(
|
|
276
|
+
"\n...CLIENT_CREDENTIAL_FILE is not set. Using default Application Default Credentials (ADC)."
|
|
277
|
+
);
|
|
278
|
+
console.log(
|
|
279
|
+
"...if you have requested any sensitive scopes, you'll see 'This app is blocked message.'"
|
|
280
|
+
);
|
|
281
|
+
console.log(
|
|
282
|
+
"...To allow them see - https://github.com/brucemcpherson/gas-fakes/blob/main/GETTING_STARTED.md\n"
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const projectId = GCP_PROJECT_ID;
|
|
287
|
+
const activeConfig = AC || "default";
|
|
288
|
+
|
|
289
|
+
console.log("Revoking previous credentials...");
|
|
290
|
+
try {
|
|
291
|
+
execSync("gcloud auth revoke --quiet", { stdio: "ignore" });
|
|
292
|
+
} catch (e) {
|
|
293
|
+
/* ignore */
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
execSync("gcloud auth application-default revoke --quiet", {
|
|
297
|
+
stdio: "ignore",
|
|
298
|
+
});
|
|
299
|
+
} catch (e) {
|
|
300
|
+
/* ignore */
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
console.log(`Ensuring gcloud configuration '${activeConfig}' exists...`);
|
|
304
|
+
try {
|
|
305
|
+
execSync(`gcloud config configurations describe "${activeConfig}"`, {
|
|
306
|
+
stdio: "ignore",
|
|
307
|
+
});
|
|
308
|
+
console.log(`Configuration '${activeConfig}' already exists.`);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.log(`Configuration '${activeConfig}' not found. Creating it...`);
|
|
311
|
+
runCommand(`gcloud config configurations create "${activeConfig}"`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log(`Activating gcloud configuration: ${activeConfig}`);
|
|
315
|
+
runCommand(`gcloud config configurations activate "${activeConfig}"`);
|
|
316
|
+
|
|
317
|
+
console.log(`Setting project to: ${projectId}`);
|
|
318
|
+
runCommand(`gcloud config set project ${projectId}`);
|
|
319
|
+
runCommand(`gcloud config set billing/quota_project ${projectId}`);
|
|
320
|
+
|
|
321
|
+
console.log("Initiating user login...");
|
|
322
|
+
runCommand(`gcloud auth login ${driveAccessFlag}`);
|
|
323
|
+
|
|
324
|
+
console.log("Initiating Application Default Credentials (ADC) login...");
|
|
325
|
+
runCommand(
|
|
326
|
+
`gcloud auth application-default login --scopes="${scopes}" ${clientFlag}`
|
|
327
|
+
);
|
|
328
|
+
runCommand(`gcloud auth application-default set-quota-project ${projectId}`);
|
|
329
|
+
|
|
330
|
+
// --- Verification ---
|
|
331
|
+
console.log("\nVerifying configuration...");
|
|
332
|
+
|
|
333
|
+
const gcloudConfigDir =
|
|
334
|
+
process.env.CLOUDSDK_CONFIG || path.join(os.homedir(), ".config", "gcloud");
|
|
335
|
+
const activeConfigPath = path.join(gcloudConfigDir, "active_config");
|
|
336
|
+
|
|
337
|
+
let currentConfig = "unknown";
|
|
338
|
+
if (fs.existsSync(activeConfigPath)) {
|
|
339
|
+
currentConfig = fs.readFileSync(activeConfigPath, "utf8").trim();
|
|
340
|
+
} else {
|
|
341
|
+
console.warn(
|
|
342
|
+
`Warning: Could not find active_config file at ${activeConfigPath}`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const currentProject = execSync("gcloud config get project")
|
|
347
|
+
.toString()
|
|
348
|
+
.trim();
|
|
349
|
+
console.log(
|
|
350
|
+
`Active config is ${currentConfig} - project is ${currentProject}`
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
console.log("\nFetching token information...");
|
|
354
|
+
const userToken = execSync("gcloud auth print-access-token")
|
|
355
|
+
.toString()
|
|
356
|
+
.trim();
|
|
357
|
+
const appDefaultToken = execSync(
|
|
358
|
+
"gcloud auth application-default print-access-token"
|
|
359
|
+
)
|
|
360
|
+
.toString()
|
|
361
|
+
.trim();
|
|
362
|
+
|
|
363
|
+
console.log("\n...user token scopes");
|
|
364
|
+
runCommand(
|
|
365
|
+
`curl https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=${userToken}`
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
console.log("\n...application default token scopes");
|
|
369
|
+
runCommand(
|
|
370
|
+
`curl https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=${appDefaultToken}`
|
|
371
|
+
);
|
|
372
|
+
console.log("\nAuthentication process finished.");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Handles the 'enableAPIs' command to enable necessary Google Cloud services.
|
|
377
|
+
*/
|
|
378
|
+
export function enableGoogleAPIs() {
|
|
379
|
+
// First, check if gcloud CLI is available.
|
|
380
|
+
checkForGcloudCli();
|
|
381
|
+
|
|
382
|
+
const services = [
|
|
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
|
+
console.log("Enabling necessary Google Cloud services...");
|
|
392
|
+
runCommand(`gcloud services enable ${services.join(" ")}`);
|
|
393
|
+
console.log("Services enabled successfully.");
|
|
394
|
+
}
|
|
@@ -16,7 +16,8 @@ import { newFakeProtection } from "./fakeprotection.js";
|
|
|
16
16
|
import { newFakeOverGridImage } from "./fakeovergridimage.js";
|
|
17
17
|
|
|
18
18
|
import { Syncit } from "../../support/syncit.js";
|
|
19
|
-
|
|
19
|
+
import { XMLParser } from "fast-xml-parser";
|
|
20
|
+
import { nullable } from "zod/v4";
|
|
20
21
|
|
|
21
22
|
const { is, isEnum } = Utils;
|
|
22
23
|
|
|
@@ -1067,28 +1068,98 @@ export class FakeSheet {
|
|
|
1067
1068
|
return [];
|
|
1068
1069
|
}
|
|
1069
1070
|
|
|
1070
|
-
// This function is used for testing getImages without a worker.
|
|
1071
|
-
// async test_getImages() {
|
|
1072
|
-
// const url = `https://docs.google.com/spreadsheets/export?exportFormat=xlsx&id=${
|
|
1073
|
-
// this.__parent.__meta.spreadsheetId
|
|
1074
|
-
// }&access_token=${ScriptApp.getOAuthToken()}`;
|
|
1075
|
-
// const res = UrlFetchApp.fetch(url);
|
|
1076
|
-
// const buf = new Uint8Array(res.getBlob()._data).buffer;
|
|
1077
|
-
// const workbook = new ExcelJS.Workbook();
|
|
1078
|
-
// await workbook.xlsx.load(buf);
|
|
1079
|
-
// const worksheet = workbook.worksheets[this.getIndex() - 1];
|
|
1080
|
-
// const images = worksheet.getImages();
|
|
1081
|
-
// console.log(images);
|
|
1082
|
-
// }
|
|
1083
|
-
|
|
1084
1071
|
getImages() {
|
|
1085
|
-
const url = `https://docs.google.com/spreadsheets/export?exportFormat=xlsx&id=${
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1072
|
+
const url = `https://docs.google.com/spreadsheets/export?exportFormat=xlsx&id=${this.__parent.__meta.spreadsheetId}`;
|
|
1073
|
+
const res = UrlFetchApp.fetch(url, {
|
|
1074
|
+
headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() },
|
|
1075
|
+
});
|
|
1076
|
+
const blob = res.getBlob().setContentType("application/zip");
|
|
1077
|
+
const unzipped = Utilities.unzip(blob);
|
|
1078
|
+
const xmlObj = unzipped.reduce((o, b) => {
|
|
1079
|
+
const filename = b.getName();
|
|
1080
|
+
const parser = new XMLParser({ ignoreAttributes: false });
|
|
1081
|
+
try {
|
|
1082
|
+
const p = parser.parse(b.getDataAsString());
|
|
1083
|
+
o[filename] = p;
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
// console.log(err);
|
|
1086
|
+
}
|
|
1087
|
+
return o;
|
|
1088
|
+
}, {});
|
|
1089
|
+
const tempObj = { sheets: {} };
|
|
1090
|
+
if (xmlObj["xl/workbook.xml"]) {
|
|
1091
|
+
const t = xmlObj[
|
|
1092
|
+
"xl/_rels/workbook.xml.rels"
|
|
1093
|
+
].Relationships.Relationship.reduce(
|
|
1094
|
+
(o, e) => ((o[e["@_Id"]] = e["@_Target"]), o),
|
|
1095
|
+
{}
|
|
1096
|
+
);
|
|
1097
|
+
xmlObj["xl/workbook.xml"].workbook.sheets.sheet.forEach((e) => {
|
|
1098
|
+
const target = `xl/${t[e["@_r:id"]]}`;
|
|
1099
|
+
const k = `xl/worksheets/_rels/${target.split("/").pop()}.rels`;
|
|
1100
|
+
let drawing;
|
|
1101
|
+
if (Array.isArray(xmlObj[k].Relationships.Relationship)) {
|
|
1102
|
+
xmlObj[k].Relationships.Relationship.forEach((f) => {
|
|
1103
|
+
if (f["@_Type"].split("/").pop() == "drawing") {
|
|
1104
|
+
drawing = `xl/drawings/${f["@_Target"].split("/").pop()}`;
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
} else {
|
|
1108
|
+
drawing = `xl/drawings/${xmlObj[k].Relationships.Relationship[
|
|
1109
|
+
"@_Target"
|
|
1110
|
+
]
|
|
1111
|
+
.split("/")
|
|
1112
|
+
.pop()}`;
|
|
1113
|
+
}
|
|
1114
|
+
const imgs = [];
|
|
1115
|
+
if (
|
|
1116
|
+
xmlObj[drawing]["xdr:wsDr"] &&
|
|
1117
|
+
xmlObj[drawing]["xdr:wsDr"]["xdr:oneCellAnchor"]
|
|
1118
|
+
) {
|
|
1119
|
+
const t = xmlObj[drawing]["xdr:wsDr"]["xdr:oneCellAnchor"];
|
|
1120
|
+
if (Array.isArray(t)) {
|
|
1121
|
+
t.forEach((f) => {
|
|
1122
|
+
imgs.push({
|
|
1123
|
+
col: f["xdr:from"]["xdr:col"],
|
|
1124
|
+
row: f["xdr:from"]["xdr:row"],
|
|
1125
|
+
anchorCellXOffset: f["xdr:from"]["xdr:colOff"] / 9525,
|
|
1126
|
+
anchorCellYOffset: f["xdr:from"]["xdr:rowOff"] / 9525,
|
|
1127
|
+
width: f["xdr:ext"]["@_cx"] / 9525,
|
|
1128
|
+
height: f["xdr:ext"]["@_cy"] / 9525,
|
|
1129
|
+
innerCell:
|
|
1130
|
+
f["xdr:from"]["xdr:colOff"] == 0 &&
|
|
1131
|
+
f["xdr:from"]["xdr:rowOff"] == 0,
|
|
1132
|
+
});
|
|
1133
|
+
});
|
|
1134
|
+
} else {
|
|
1135
|
+
imgs.push({
|
|
1136
|
+
col: t["xdr:from"]["xdr:col"],
|
|
1137
|
+
row: t["xdr:from"]["xdr:row"],
|
|
1138
|
+
anchorCellXOffset: t["xdr:from"]["xdr:colOff"] / 9525,
|
|
1139
|
+
anchorCellYOffset: t["xdr:from"]["xdr:rowOff"] / 9525,
|
|
1140
|
+
width: t["xdr:ext"]["@_cx"] / 9525,
|
|
1141
|
+
height: t["xdr:ext"]["@_cy"] / 9525,
|
|
1142
|
+
innerCell:
|
|
1143
|
+
f["xdr:from"]["xdr:colOff"] == 0 &&
|
|
1144
|
+
f["xdr:from"]["xdr:rowOff"] == 0,
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
tempObj.sheets[e["@_name"]] = {
|
|
1149
|
+
...e,
|
|
1150
|
+
name: `xl/${t[e["@_r:id"]]}`,
|
|
1151
|
+
drawing,
|
|
1152
|
+
imgs: imgs.filter(({ innerCell }) => !innerCell), // Images over cells are retrieved.
|
|
1153
|
+
};
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
const sheetName = this.getName();
|
|
1157
|
+
const o = tempObj.sheets[sheetName];
|
|
1158
|
+
if (o.imgs && o.imgs.length > 0) {
|
|
1159
|
+
const sheet = this;
|
|
1160
|
+
return o.imgs.map((e) => newFakeOverGridImage(sheet, e));
|
|
1161
|
+
}
|
|
1162
|
+
return [];
|
|
1092
1163
|
}
|
|
1093
1164
|
|
|
1094
1165
|
__batchUpdate({ spreadsheetId, requests }) {
|
package/src/support/sxxlsx.js
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import got from "got";
|
|
2
|
-
import ExcelJS from "exceljs";
|
|
1
|
+
// import got from "got";
|
|
2
|
+
// import ExcelJS from "exceljs";
|
|
3
3
|
|
|
4
|
-
async function __getWorkbook(url) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
4
|
+
// async function __getWorkbook(url) {
|
|
5
|
+
// const res = await got(url, { responseType: "buffer" });
|
|
6
|
+
// let buf;
|
|
7
|
+
// if (res.rawBody && Buffer.isBuffer(res.rawBody)) {
|
|
8
|
+
// buf = res.rawBody;
|
|
9
|
+
// } else if (res.body && Buffer.isBuffer(res.body)) {
|
|
10
|
+
// buf = res.body;
|
|
11
|
+
// } else {
|
|
12
|
+
// throw new Error(res);
|
|
13
|
+
// }
|
|
14
|
+
// const workbook = new ExcelJS.Workbook();
|
|
15
|
+
// await workbook.xlsx.load(buf);
|
|
16
|
+
// return workbook;
|
|
17
|
+
// }
|
|
18
18
|
|
|
19
|
-
export const sxGetImagesFromXlsx = async (_, { url, idx }) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
19
|
+
// export const sxGetImagesFromXlsx = async (_, { url, idx }) => {
|
|
20
|
+
// const workbook = await __getWorkbook(url);
|
|
21
|
+
// const worksheet = workbook.worksheets[idx];
|
|
22
|
+
// const ar = worksheet.getImages().reduce((arr, image) => {
|
|
23
|
+
// const imageId = image.imageId;
|
|
24
|
+
// if (image.range.tl.nativeColOff != 0 && image.range.tl.nativeRowOff != 0) {
|
|
25
|
+
// const col = image.range.tl.nativeCol;
|
|
26
|
+
// const row = image.range.tl.nativeRow;
|
|
27
|
+
// const { width, height } = image.range.ext;
|
|
28
|
+
// arr.push({
|
|
29
|
+
// imageId,
|
|
30
|
+
// row,
|
|
31
|
+
// col,
|
|
32
|
+
// width,
|
|
33
|
+
// height,
|
|
34
|
+
// anchorCellXOffset: image.range.tl.nativeColOff,
|
|
35
|
+
// anchorCellYOffset: image.range.tl.nativeRowOff,
|
|
36
|
+
// });
|
|
37
|
+
// }
|
|
38
|
+
// return arr;
|
|
39
|
+
// }, []);
|
|
40
|
+
// return ar;
|
|
41
|
+
// };
|
package/src/support/syncit.js
CHANGED
|
@@ -266,7 +266,6 @@ const fxInit = ({
|
|
|
266
266
|
cachePath = cacheDefaultPath,
|
|
267
267
|
propertiesPath = propertiesDefaultPath,
|
|
268
268
|
} = {}) => {
|
|
269
|
-
|
|
270
269
|
// this is the path of the runing main process
|
|
271
270
|
const mainDir = path.dirname(process.argv[1]);
|
|
272
271
|
|
|
@@ -382,7 +381,7 @@ const fxGmail = (args) =>
|
|
|
382
381
|
idField: "id",
|
|
383
382
|
});
|
|
384
383
|
|
|
385
|
-
const fxGetImagesFromXlsx = (args) => callSync("sxGetImagesFromXlsx", args);
|
|
384
|
+
// const fxGetImagesFromXlsx = (args) => callSync("sxGetImagesFromXlsx", args);
|
|
386
385
|
|
|
387
386
|
export const Syncit = {
|
|
388
387
|
fxFetch,
|
|
@@ -400,5 +399,5 @@ export const Syncit = {
|
|
|
400
399
|
fxDocs,
|
|
401
400
|
fxForms,
|
|
402
401
|
fxGmail,
|
|
403
|
-
fxGetImagesFromXlsx
|
|
402
|
+
// fxGetImagesFromXlsx
|
|
404
403
|
};
|