@mcpher/gas-fakes 1.2.17 → 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.
Files changed (3) hide show
  1. package/gas-fakes.js +17 -2
  2. package/package.json +1 -1
  3. package/setup.js +151 -45
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.10";
34
+ const CLI_VERSION = "0.0.11";
35
35
  const MCP_VERSION = "0.0.3";
36
36
  const execAsync = promisify(exec);
37
37
 
@@ -538,7 +538,22 @@ async function main() {
538
538
 
539
539
  program
540
540
  .command("enableAPIs")
541
- .description("Enables the required Google Cloud APIs for the project.")
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")
542
557
  .action(enableGoogleAPIs);
543
558
 
544
559
  // MCP server command
package/package.json CHANGED
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "name": "@mcpher/gas-fakes",
34
34
  "author": "bruce mcpherson",
35
- "version": "1.2.17",
35
+ "version": "1.2.18",
36
36
  "license": "MIT",
37
37
  "main": "main.js",
38
38
  "description": "A proof of concept implementation of Apps Script Environment on Node",
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.
@@ -51,16 +80,26 @@ function runCommand(command) {
51
80
  * Handles the 'init' command to configure the .env file.
52
81
  */
53
82
  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");
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
+
56
95
  let existingConfig = {};
57
96
 
58
- // --- Load existing values from .env file if it exists ---
59
- if (fs.existsSync(envPath)) {
97
+ // Load existing values from .env file if it exists to use as defaults for prompts.
98
+ if (existsSync(envPath)) {
60
99
  console.log(
61
100
  "Found existing .env file. Loading current values as defaults."
62
101
  );
63
- existingConfig = dotenv.config({ path: envPath , quiet: true }).parsed || {};
102
+ existingConfig = dotenv.parse(readFileSync(envPath));
64
103
  }
65
104
 
66
105
  console.log("--------------------------------------------------");
@@ -140,6 +179,13 @@ export async function initializeConfiguration() {
140
179
 
141
180
  const responses = await prompts(questions);
142
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.
143
189
  if (responses.STORE_TYPE === "UPSTASH") {
144
190
  console.log(
145
191
  "Upstash storage selected. Please provide your Redis credentials."
@@ -159,50 +205,72 @@ export async function initializeConfiguration() {
159
205
  },
160
206
  ];
161
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
+ }
162
213
  Object.assign(responses, upstashResponses);
163
214
  }
164
215
 
165
- const finalConfig = { ...existingConfig, ...responses };
166
-
167
216
  console.log("--------------------------------------------------");
168
217
  console.log(`Writing configuration to ${envPath}...`);
169
218
 
170
- let envContent = `
219
+ if (!existsSync(envPath)) {
220
+ // --- Create a new .env file from a template ---
221
+ let envContent = `
171
222
  # Google Cloud Project ID (required)
172
- GCP_PROJECT_ID="${finalConfig.GCP_PROJECT_ID || ""}"
223
+ GCP_PROJECT_ID="${responses.GCP_PROJECT_ID || ""}"
173
224
 
174
225
  # Path to OAuth client credentials for restricted scopes (optional)
175
- CLIENT_CREDENTIAL_FILE="${finalConfig.CLIENT_CREDENTIAL_FILE || ""}"
226
+ CLIENT_CREDENTIAL_FILE="${responses.CLIENT_CREDENTIAL_FILE || ""}"
176
227
 
177
228
  # A test file ID for checking authentication (optional)
178
- DRIVE_TEST_FILE_ID="${finalConfig.DRIVE_TEST_FILE_ID || ""}"
229
+ DRIVE_TEST_FILE_ID="${responses.DRIVE_TEST_FILE_ID || ""}"
179
230
 
180
231
  # Storage configuration for PropertiesService and CacheService ('FILE' or 'UPSTASH')
181
- STORE_TYPE="${finalConfig.STORE_TYPE || "FILE"}"
232
+ STORE_TYPE="${responses.STORE_TYPE || "FILE"}"
182
233
 
183
234
  # Logging destination for Logger.log() ('CONSOLE', 'CLOUD', 'BOTH', 'NONE')
184
- LOG_DESTINATION="${finalConfig.LOG_DESTINATION || "CONSOLE"}"
235
+ LOG_DESTINATION="${responses.LOG_DESTINATION || "CONSOLE"}"
185
236
 
186
237
  # Scopes for authentication
187
238
  # 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 || ""}"
239
+ DEFAULT_SCOPES="${responses.DEFAULT_SCOPES || ""}"
240
+ EXTRA_SCOPES="${responses.EXTRA_SCOPES || ""}"
190
241
  `.trim();
191
242
 
192
- if (
193
- finalConfig.UPSTASH_REDIS_REST_URL &&
194
- finalConfig.UPSTASH_REDIS_REST_TOKEN
195
- ) {
196
- envContent += `
243
+ if (responses.STORE_TYPE === "UPSTASH") {
244
+ envContent += `
197
245
 
198
246
  # 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}"
247
+ UPSTASH_REDIS_REST_URL="${responses.UPSTASH_REDIS_REST_URL || ""}"
248
+ UPSTASH_REDIS_REST_TOKEN="${responses.UPSTASH_REDIS_REST_TOKEN || ""}"
201
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");
202
272
  }
203
273
 
204
- fs.writeFileSync(envPath, envContent);
205
-
206
274
  console.log("Setup complete. Your .env file has been updated.");
207
275
  console.log("--------------------------------------------------");
208
276
  }
@@ -217,13 +285,13 @@ export function authenticateUser() {
217
285
  const rootDirectory = process.cwd();
218
286
  const envPath = path.join(rootDirectory, ".env");
219
287
 
220
- if (!fs.existsSync(envPath)) {
288
+ if (!existsSync(envPath)) {
221
289
  console.error(`Error: .env file not found at '${envPath}'`);
222
290
  console.error("Please run './gas-fakes.js init' first.");
223
291
  process.exit(1);
224
292
  }
225
293
 
226
- dotenv.config({ path: envPath });
294
+ dotenv.config({ path: envPath, quiet: true });
227
295
 
228
296
  const {
229
297
  GCP_PROJECT_ID,
@@ -263,7 +331,7 @@ export function authenticateUser() {
263
331
  clientPath = path.join(rootDirectory, clientPath);
264
332
  }
265
333
 
266
- if (fs.existsSync(clientPath)) {
334
+ if (existsSync(clientPath)) {
267
335
  clientFlag = `--client-id-file="${clientPath}"`;
268
336
  } else {
269
337
  console.error(
@@ -335,8 +403,8 @@ export function authenticateUser() {
335
403
  const activeConfigPath = path.join(gcloudConfigDir, "active_config");
336
404
 
337
405
  let currentConfig = "unknown";
338
- if (fs.existsSync(activeConfigPath)) {
339
- currentConfig = fs.readFileSync(activeConfigPath, "utf8").trim();
406
+ if (existsSync(activeConfigPath)) {
407
+ currentConfig = readFileSync(activeConfigPath, "utf8").trim();
340
408
  } else {
341
409
  console.warn(
342
410
  `Warning: Could not find active_config file at ${activeConfigPath}`
@@ -373,22 +441,60 @@ export function authenticateUser() {
373
441
  }
374
442
 
375
443
  /**
376
- * Handles the 'enableAPIs' command to enable necessary Google Cloud services.
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.
377
446
  */
378
- export function enableGoogleAPIs() {
379
- // First, check if gcloud CLI is available.
447
+ export function enableGoogleAPIs(options) {
380
448
  checkForGcloudCli();
381
449
 
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.");
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
+ }
394
500
  }