@mcpher/gas-fakes 1.2.16 → 1.2.18
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 +54 -10
- package/package.json +2 -1
- package/setup.js +500 -0
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.11";
|
|
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,40 @@ 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(
|
|
542
|
+
"Enables or disables required Google Cloud APIs for the project."
|
|
543
|
+
)
|
|
544
|
+
.option("--all", "Enable all default Google Cloud APIs.")
|
|
545
|
+
.option("--edrive", "Enable drive.googleapis.com")
|
|
546
|
+
.option("--ddrive", "Disable drive.googleapis.com")
|
|
547
|
+
.option("--esheets", "Enable sheets.googleapis.com")
|
|
548
|
+
.option("--dsheets", "Disable sheets.googleapis.com")
|
|
549
|
+
.option("--eforms", "Enable forms.googleapis.com")
|
|
550
|
+
.option("--dforms", "Disable forms.googleapis.com")
|
|
551
|
+
.option("--edocs", "Enable docs.googleapis.com")
|
|
552
|
+
.option("--ddocs", "Disable docs.googleapis.com")
|
|
553
|
+
.option("--egmail", "Enable gmail.googleapis.com")
|
|
554
|
+
.option("--dgmail", "Disable gmail.googleapis.com")
|
|
555
|
+
.option("--elogging", "Enable logging.googleapis.com")
|
|
556
|
+
.option("--dlogging", "Disable logging.googleapis.com")
|
|
557
|
+
.action(enableGoogleAPIs);
|
|
558
|
+
|
|
559
|
+
// MCP server command
|
|
516
560
|
program
|
|
517
561
|
.command("mcp")
|
|
518
562
|
.description("Launch gas-fakes as an MCP server.")
|
package/package.json
CHANGED
|
@@ -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.18",
|
|
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,500 @@
|
|
|
1
|
+
// setup.js: Setup for gas-fakes.
|
|
2
|
+
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import os from "os";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
|
|
11
|
+
// --- Utility Functions ---
|
|
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
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Checks if the gcloud CLI is installed and available in the system's PATH.
|
|
43
|
+
* If not, it prints an informative message and exits the script.
|
|
44
|
+
*/
|
|
45
|
+
function checkForGcloudCli() {
|
|
46
|
+
try {
|
|
47
|
+
// Execute a simple, non-destructive command to check if gcloud exists.
|
|
48
|
+
// The output is ignored to keep the console clean on success.
|
|
49
|
+
execSync("gcloud --version", { stdio: "ignore" });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// The command failed, likely because gcloud is not installed or not in the PATH.
|
|
52
|
+
console.error("\n[Error] Google Cloud SDK (gcloud CLI) not found.");
|
|
53
|
+
console.error(
|
|
54
|
+
"This script requires the gcloud CLI to manage authentication and Google Cloud services."
|
|
55
|
+
);
|
|
56
|
+
console.error("Please install it by following the official instructions:");
|
|
57
|
+
console.error("https://cloud.google.com/sdk/gcloud");
|
|
58
|
+
process.exit(1); // Exit the script with an error code.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Helper function to run a shell command and print its output.
|
|
64
|
+
* @param {string} command The command to execute.
|
|
65
|
+
*/
|
|
66
|
+
function runCommand(command) {
|
|
67
|
+
try {
|
|
68
|
+
// Execute the command, inheriting stdio to show output/errors in real-time.
|
|
69
|
+
execSync(command, { stdio: "inherit" });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error(`\nError executing command: ${command}`);
|
|
72
|
+
// The error message from the command is already shown due to 'inherit' stdio.
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --- Exported Command Implementations ---
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Handles the 'init' command to configure the .env file.
|
|
81
|
+
*/
|
|
82
|
+
export async function initializeConfiguration() {
|
|
83
|
+
let envPath = path.join(process.cwd(), ".env");
|
|
84
|
+
const searchPath = process.cwd(); // or process.env.HOME
|
|
85
|
+
const absoluteSearchPath = path.resolve(searchPath);
|
|
86
|
+
const foundFiles = await findEnvFiles(absoluteSearchPath);
|
|
87
|
+
if (foundFiles.length > 0) {
|
|
88
|
+
// Check .env on the top directory.
|
|
89
|
+
const results = foundFiles
|
|
90
|
+
.map((file) => file.split("/"))
|
|
91
|
+
.sort((a, b) => (a.length > b.length ? 1 : -1));
|
|
92
|
+
envPath = results[0].join("/");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let existingConfig = {};
|
|
96
|
+
|
|
97
|
+
// Load existing values from .env file if it exists to use as defaults for prompts.
|
|
98
|
+
if (existsSync(envPath)) {
|
|
99
|
+
console.log(
|
|
100
|
+
"Found existing .env file. Loading current values as defaults."
|
|
101
|
+
);
|
|
102
|
+
existingConfig = dotenv.parse(readFileSync(envPath));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log("--------------------------------------------------");
|
|
106
|
+
console.log("Configuring .env for gas-fakes");
|
|
107
|
+
console.log("Press Enter to accept the default value in brackets.");
|
|
108
|
+
console.log("--------------------------------------------------");
|
|
109
|
+
|
|
110
|
+
const questions = [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
name: "GCP_PROJECT_ID",
|
|
114
|
+
message: "Enter your GCP Project ID",
|
|
115
|
+
initial: existingConfig.GCP_PROJECT_ID || "",
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: "text",
|
|
119
|
+
name: "DRIVE_TEST_FILE_ID",
|
|
120
|
+
message: "Enter a test Drive file ID for authentication checks",
|
|
121
|
+
initial: existingConfig.DRIVE_TEST_FILE_ID || "",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: "text",
|
|
125
|
+
name: "CLIENT_CREDENTIAL_FILE",
|
|
126
|
+
message: "Enter path to OAuth client credentials JSON (optional)",
|
|
127
|
+
initial: existingConfig.CLIENT_CREDENTIAL_FILE || "",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
name: "DEFAULT_SCOPES",
|
|
132
|
+
message: "Enter default scopes",
|
|
133
|
+
initial:
|
|
134
|
+
existingConfig.DEFAULT_SCOPES ||
|
|
135
|
+
"https://www.googleapis.com/auth/userinfo.email,openid,https://www.googleapis.com/auth/cloud-platform",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: "text",
|
|
139
|
+
name: "EXTRA_SCOPES",
|
|
140
|
+
message: "Enter any extra scopes (comma-separated)",
|
|
141
|
+
initial:
|
|
142
|
+
existingConfig.EXTRA_SCOPES ||
|
|
143
|
+
",https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/spreadsheets",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: "select",
|
|
147
|
+
name: "LOG_DESTINATION",
|
|
148
|
+
message: "Enter logging destination",
|
|
149
|
+
choices: [
|
|
150
|
+
{ title: "CONSOLE", value: "CONSOLE" },
|
|
151
|
+
{ title: "CLOUD", value: "CLOUD" },
|
|
152
|
+
{ title: "BOTH", value: "BOTH" },
|
|
153
|
+
{ title: "NONE", value: "NONE" },
|
|
154
|
+
],
|
|
155
|
+
initial:
|
|
156
|
+
["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
|
|
157
|
+
existingConfig.LOG_DESTINATION
|
|
158
|
+
) > -1
|
|
159
|
+
? ["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
|
|
160
|
+
existingConfig.LOG_DESTINATION
|
|
161
|
+
)
|
|
162
|
+
: 0,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
type: "select",
|
|
166
|
+
name: "STORE_TYPE",
|
|
167
|
+
message: "Enter storage type",
|
|
168
|
+
choices: [
|
|
169
|
+
{ title: "FILE", value: "FILE" },
|
|
170
|
+
{ title: "UPSTASH", value: "UPSTASH" },
|
|
171
|
+
],
|
|
172
|
+
initial:
|
|
173
|
+
["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE?.toUpperCase()) >
|
|
174
|
+
-1
|
|
175
|
+
? ["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE.toUpperCase())
|
|
176
|
+
: 0,
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
const responses = await prompts(questions);
|
|
181
|
+
|
|
182
|
+
// If the user cancels (e.g., Ctrl+C), prompts returns undefined for the keys.
|
|
183
|
+
if (typeof responses.GCP_PROJECT_ID === "undefined") {
|
|
184
|
+
console.log("Initialization cancelled.");
|
|
185
|
+
return; // Exit the function without writing to file.
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// If Upstash is selected, ask for its credentials.
|
|
189
|
+
if (responses.STORE_TYPE === "UPSTASH") {
|
|
190
|
+
console.log(
|
|
191
|
+
"Upstash storage selected. Please provide your Redis credentials."
|
|
192
|
+
);
|
|
193
|
+
const upstashQuestions = [
|
|
194
|
+
{
|
|
195
|
+
type: "text",
|
|
196
|
+
name: "UPSTASH_REDIS_REST_URL",
|
|
197
|
+
message: "Enter your Upstash Redis REST URL",
|
|
198
|
+
initial: existingConfig.UPSTASH_REDIS_REST_URL || "",
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
type: "text",
|
|
202
|
+
name: "UPSTASH_REDIS_REST_TOKEN",
|
|
203
|
+
message: "Enter your Upstash Redis REST Token",
|
|
204
|
+
initial: existingConfig.UPSTASH_REDIS_REST_TOKEN || "",
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
const upstashResponses = await prompts(upstashQuestions);
|
|
208
|
+
|
|
209
|
+
if (typeof upstashResponses.UPSTASH_REDIS_REST_URL === "undefined") {
|
|
210
|
+
console.log("Initialization cancelled during Upstash configuration.");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
Object.assign(responses, upstashResponses);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log("--------------------------------------------------");
|
|
217
|
+
console.log(`Writing configuration to ${envPath}...`);
|
|
218
|
+
|
|
219
|
+
if (!existsSync(envPath)) {
|
|
220
|
+
// --- Create a new .env file from a template ---
|
|
221
|
+
let envContent = `
|
|
222
|
+
# Google Cloud Project ID (required)
|
|
223
|
+
GCP_PROJECT_ID="${responses.GCP_PROJECT_ID || ""}"
|
|
224
|
+
|
|
225
|
+
# Path to OAuth client credentials for restricted scopes (optional)
|
|
226
|
+
CLIENT_CREDENTIAL_FILE="${responses.CLIENT_CREDENTIAL_FILE || ""}"
|
|
227
|
+
|
|
228
|
+
# A test file ID for checking authentication (optional)
|
|
229
|
+
DRIVE_TEST_FILE_ID="${responses.DRIVE_TEST_FILE_ID || ""}"
|
|
230
|
+
|
|
231
|
+
# Storage configuration for PropertiesService and CacheService ('FILE' or 'UPSTASH')
|
|
232
|
+
STORE_TYPE="${responses.STORE_TYPE || "FILE"}"
|
|
233
|
+
|
|
234
|
+
# Logging destination for Logger.log() ('CONSOLE', 'CLOUD', 'BOTH', 'NONE')
|
|
235
|
+
LOG_DESTINATION="${responses.LOG_DESTINATION || "CONSOLE"}"
|
|
236
|
+
|
|
237
|
+
# Scopes for authentication
|
|
238
|
+
# these are the scopes set by default - take some of these out if you want to minimize access
|
|
239
|
+
DEFAULT_SCOPES="${responses.DEFAULT_SCOPES || ""}"
|
|
240
|
+
EXTRA_SCOPES="${responses.EXTRA_SCOPES || ""}"
|
|
241
|
+
`.trim();
|
|
242
|
+
|
|
243
|
+
if (responses.STORE_TYPE === "UPSTASH") {
|
|
244
|
+
envContent += `
|
|
245
|
+
|
|
246
|
+
# Upstash credentials (only used if STORE_TYPE is 'UPSTASH')
|
|
247
|
+
UPSTASH_REDIS_REST_URL="${responses.UPSTASH_REDIS_REST_URL || ""}"
|
|
248
|
+
UPSTASH_REDIS_REST_TOKEN="${responses.UPSTASH_REDIS_REST_TOKEN || ""}"
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
writeFileSync(envPath, envContent, "utf8");
|
|
252
|
+
} else {
|
|
253
|
+
// --- Update the existing .env file ---
|
|
254
|
+
let envContent = readFileSync(envPath, "utf8");
|
|
255
|
+
|
|
256
|
+
const configToUpdate = { ...responses };
|
|
257
|
+
|
|
258
|
+
for (const key of Object.keys(configToUpdate)) {
|
|
259
|
+
const value = configToUpdate[key] || "";
|
|
260
|
+
const keyRegex = new RegExp(`^\\s*${key}\\s*=.*$`, "m");
|
|
261
|
+
|
|
262
|
+
if (keyRegex.test(envContent)) {
|
|
263
|
+
envContent = envContent.replace(keyRegex, `${key}="${value}"`);
|
|
264
|
+
} else {
|
|
265
|
+
if (envContent.length > 0 && !envContent.endsWith("\n")) {
|
|
266
|
+
envContent += "\n";
|
|
267
|
+
}
|
|
268
|
+
envContent += `${key}="${value}"\n`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
writeFileSync(envPath, envContent, "utf8");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log("Setup complete. Your .env file has been updated.");
|
|
275
|
+
console.log("--------------------------------------------------");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Handles the 'auth' command to authenticate with Google Cloud.
|
|
280
|
+
*/
|
|
281
|
+
export function authenticateUser() {
|
|
282
|
+
// First, check if gcloud CLI is available.
|
|
283
|
+
checkForGcloudCli();
|
|
284
|
+
|
|
285
|
+
const rootDirectory = process.cwd();
|
|
286
|
+
const envPath = path.join(rootDirectory, ".env");
|
|
287
|
+
|
|
288
|
+
if (!existsSync(envPath)) {
|
|
289
|
+
console.error(`Error: .env file not found at '${envPath}'`);
|
|
290
|
+
console.error("Please run './gas-fakes.js init' first.");
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
dotenv.config({ path: envPath, quiet: true });
|
|
295
|
+
|
|
296
|
+
const {
|
|
297
|
+
GCP_PROJECT_ID,
|
|
298
|
+
DEFAULT_SCOPES,
|
|
299
|
+
EXTRA_SCOPES,
|
|
300
|
+
CLIENT_CREDENTIAL_FILE,
|
|
301
|
+
AC,
|
|
302
|
+
} = process.env;
|
|
303
|
+
|
|
304
|
+
if (!GCP_PROJECT_ID) {
|
|
305
|
+
console.error("Error: GCP_PROJECT_ID is not set in your .env file.");
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const defaultScopes =
|
|
310
|
+
DEFAULT_SCOPES ||
|
|
311
|
+
"https://www.googleapis.com/auth/userinfo.email,openid,https://www.googleapis.com/auth/cloud-platform";
|
|
312
|
+
const extraScopes =
|
|
313
|
+
EXTRA_SCOPES ||
|
|
314
|
+
"https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/spreadsheets";
|
|
315
|
+
|
|
316
|
+
let scopes = defaultScopes;
|
|
317
|
+
if (extraScopes && extraScopes.length > 0) {
|
|
318
|
+
scopes += (extraScopes.startsWith(",") ? "" : ",") + extraScopes;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const driveAccessFlag = "--enable-gdrive-access";
|
|
322
|
+
|
|
323
|
+
console.log(`...requesting scopes ${scopes}`);
|
|
324
|
+
|
|
325
|
+
let clientFlag = "";
|
|
326
|
+
if (CLIENT_CREDENTIAL_FILE) {
|
|
327
|
+
console.log("...attempting to use enhanced client credentials");
|
|
328
|
+
|
|
329
|
+
let clientPath = CLIENT_CREDENTIAL_FILE;
|
|
330
|
+
if (!path.isAbsolute(clientPath)) {
|
|
331
|
+
clientPath = path.join(rootDirectory, clientPath);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (existsSync(clientPath)) {
|
|
335
|
+
clientFlag = `--client-id-file="${clientPath}"`;
|
|
336
|
+
} else {
|
|
337
|
+
console.error(
|
|
338
|
+
`Error: Client credential file specified in .env not found at '${clientPath}'`
|
|
339
|
+
);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
console.log(
|
|
344
|
+
"\n...CLIENT_CREDENTIAL_FILE is not set. Using default Application Default Credentials (ADC)."
|
|
345
|
+
);
|
|
346
|
+
console.log(
|
|
347
|
+
"...if you have requested any sensitive scopes, you'll see 'This app is blocked message.'"
|
|
348
|
+
);
|
|
349
|
+
console.log(
|
|
350
|
+
"...To allow them see - https://github.com/brucemcpherson/gas-fakes/blob/main/GETTING_STARTED.md\n"
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const projectId = GCP_PROJECT_ID;
|
|
355
|
+
const activeConfig = AC || "default";
|
|
356
|
+
|
|
357
|
+
console.log("Revoking previous credentials...");
|
|
358
|
+
try {
|
|
359
|
+
execSync("gcloud auth revoke --quiet", { stdio: "ignore" });
|
|
360
|
+
} catch (e) {
|
|
361
|
+
/* ignore */
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
execSync("gcloud auth application-default revoke --quiet", {
|
|
365
|
+
stdio: "ignore",
|
|
366
|
+
});
|
|
367
|
+
} catch (e) {
|
|
368
|
+
/* ignore */
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
console.log(`Ensuring gcloud configuration '${activeConfig}' exists...`);
|
|
372
|
+
try {
|
|
373
|
+
execSync(`gcloud config configurations describe "${activeConfig}"`, {
|
|
374
|
+
stdio: "ignore",
|
|
375
|
+
});
|
|
376
|
+
console.log(`Configuration '${activeConfig}' already exists.`);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.log(`Configuration '${activeConfig}' not found. Creating it...`);
|
|
379
|
+
runCommand(`gcloud config configurations create "${activeConfig}"`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
console.log(`Activating gcloud configuration: ${activeConfig}`);
|
|
383
|
+
runCommand(`gcloud config configurations activate "${activeConfig}"`);
|
|
384
|
+
|
|
385
|
+
console.log(`Setting project to: ${projectId}`);
|
|
386
|
+
runCommand(`gcloud config set project ${projectId}`);
|
|
387
|
+
runCommand(`gcloud config set billing/quota_project ${projectId}`);
|
|
388
|
+
|
|
389
|
+
console.log("Initiating user login...");
|
|
390
|
+
runCommand(`gcloud auth login ${driveAccessFlag}`);
|
|
391
|
+
|
|
392
|
+
console.log("Initiating Application Default Credentials (ADC) login...");
|
|
393
|
+
runCommand(
|
|
394
|
+
`gcloud auth application-default login --scopes="${scopes}" ${clientFlag}`
|
|
395
|
+
);
|
|
396
|
+
runCommand(`gcloud auth application-default set-quota-project ${projectId}`);
|
|
397
|
+
|
|
398
|
+
// --- Verification ---
|
|
399
|
+
console.log("\nVerifying configuration...");
|
|
400
|
+
|
|
401
|
+
const gcloudConfigDir =
|
|
402
|
+
process.env.CLOUDSDK_CONFIG || path.join(os.homedir(), ".config", "gcloud");
|
|
403
|
+
const activeConfigPath = path.join(gcloudConfigDir, "active_config");
|
|
404
|
+
|
|
405
|
+
let currentConfig = "unknown";
|
|
406
|
+
if (existsSync(activeConfigPath)) {
|
|
407
|
+
currentConfig = readFileSync(activeConfigPath, "utf8").trim();
|
|
408
|
+
} else {
|
|
409
|
+
console.warn(
|
|
410
|
+
`Warning: Could not find active_config file at ${activeConfigPath}`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const currentProject = execSync("gcloud config get project")
|
|
415
|
+
.toString()
|
|
416
|
+
.trim();
|
|
417
|
+
console.log(
|
|
418
|
+
`Active config is ${currentConfig} - project is ${currentProject}`
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
console.log("\nFetching token information...");
|
|
422
|
+
const userToken = execSync("gcloud auth print-access-token")
|
|
423
|
+
.toString()
|
|
424
|
+
.trim();
|
|
425
|
+
const appDefaultToken = execSync(
|
|
426
|
+
"gcloud auth application-default print-access-token"
|
|
427
|
+
)
|
|
428
|
+
.toString()
|
|
429
|
+
.trim();
|
|
430
|
+
|
|
431
|
+
console.log("\n...user token scopes");
|
|
432
|
+
runCommand(
|
|
433
|
+
`curl https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=${userToken}`
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
console.log("\n...application default token scopes");
|
|
437
|
+
runCommand(
|
|
438
|
+
`curl https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=${appDefaultToken}`
|
|
439
|
+
);
|
|
440
|
+
console.log("\nAuthentication process finished.");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Handles the 'enableAPIs' command to enable or disable necessary Google Cloud services based on options.
|
|
445
|
+
* @param {object} options Options object provided by commander.js.
|
|
446
|
+
*/
|
|
447
|
+
export function enableGoogleAPIs(options) {
|
|
448
|
+
checkForGcloudCli();
|
|
449
|
+
|
|
450
|
+
const API_SERVICES = {
|
|
451
|
+
drive: "drive.googleapis.com",
|
|
452
|
+
sheets: "sheets.googleapis.com",
|
|
453
|
+
forms: "forms.googleapis.com",
|
|
454
|
+
docs: "docs.googleapis.com",
|
|
455
|
+
gmail: "gmail.googleapis.com",
|
|
456
|
+
logging: "logging.googleapis.com",
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const servicesToEnable = new Set();
|
|
460
|
+
const servicesToDisable = new Set();
|
|
461
|
+
if (options.all || Object.keys(options).length === 0) {
|
|
462
|
+
Object.values(API_SERVICES).forEach((service) =>
|
|
463
|
+
servicesToEnable.add(service)
|
|
464
|
+
);
|
|
465
|
+
} else {
|
|
466
|
+
for (const key in API_SERVICES) {
|
|
467
|
+
if (options[`e${key}`]) {
|
|
468
|
+
servicesToEnable.add(API_SERVICES[key]);
|
|
469
|
+
}
|
|
470
|
+
if (options[`d${key}`]) {
|
|
471
|
+
servicesToDisable.add(API_SERVICES[key]);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (servicesToEnable.size > 0) {
|
|
476
|
+
const enableList = Array.from(servicesToEnable);
|
|
477
|
+
console.log(`Enabling Google Cloud services: ${enableList.join(", ")}...`);
|
|
478
|
+
runCommand(`gcloud services enable ${enableList.join(" ")}`);
|
|
479
|
+
console.log("Services enabled successfully.");
|
|
480
|
+
}
|
|
481
|
+
if (servicesToDisable.size > 0) {
|
|
482
|
+
const disableList = Array.from(servicesToDisable);
|
|
483
|
+
console.log(
|
|
484
|
+
`Disabling Google Cloud services: ${disableList.join(", ")}...`
|
|
485
|
+
);
|
|
486
|
+
runCommand(`gcloud services disable ${disableList.join(" ")}`);
|
|
487
|
+
console.log("Services disabled successfully.");
|
|
488
|
+
}
|
|
489
|
+
if (
|
|
490
|
+
servicesToEnable.size === 0 &&
|
|
491
|
+
servicesToDisable.size === 0 &&
|
|
492
|
+
Object.keys(options).length > 0 &&
|
|
493
|
+
!options.all
|
|
494
|
+
) {
|
|
495
|
+
console.log("No specific APIs were selected to enable or disable.");
|
|
496
|
+
console.log(
|
|
497
|
+
"Use '--all' to enable all default APIs, or specify flags like '--edrive' or '--ddrive'."
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
}
|