@mcpher/gas-fakes 1.0.20 → 1.0.21

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 (36) hide show
  1. package/README.md +57 -18
  2. package/gprompts/{gas-inventory.mjs → gas-inventory.js} +25 -13
  3. package/gprompts/gas-inventory.json +116 -20
  4. package/gprompts/inventory-list.json +1 -1
  5. package/gprompts/package-lock.json +234 -0
  6. package/gprompts/package.json +1 -0
  7. package/package.json +3 -1
  8. package/src/index.js +2 -1
  9. package/src/services/advdocs/app.js +4 -23
  10. package/src/services/advdrive/app.js +6 -28
  11. package/src/services/advforms/app.js +6 -25
  12. package/src/services/advgmail/app.js +6 -26
  13. package/src/services/advsheets/app.js +6 -26
  14. package/src/services/advslides/app.js +6 -28
  15. package/src/services/common/lazyloader.js +22 -0
  16. package/src/services/documentapp/app.js +8 -42
  17. package/src/services/documentapp/appenderhelpers.js +2 -2
  18. package/src/services/documentapp/fakeparagraph.js +1 -1
  19. package/src/services/driveapp/app.js +6 -28
  20. package/src/services/formapp/app.js +5 -40
  21. package/src/services/gmailapp/app.js +7 -40
  22. package/src/services/logger/app.js +8 -0
  23. package/src/services/logger/fakelogger.js +162 -0
  24. package/src/services/scriptapp/app.js +7 -1
  25. package/src/services/scriptapp/behavior.js +1 -1
  26. package/src/services/session/app.js +10 -0
  27. package/src/services/slidesapp/app.js +5 -40
  28. package/src/services/spreadsheetapp/app.js +6 -50
  29. package/src/services/spreadsheetapp/fakesheet.js +3 -4
  30. package/src/services/stores/app.js +0 -1
  31. package/src/services/urlfetchapp/app.js +0 -1
  32. package/src/services/utilities/app.js +6 -20
  33. package/src/support/proxies.js +16 -0
  34. package/src/support/syncit.js +2 -2
  35. package/src/services/base/app.js +0 -33
  36. /package/src/services/{base → session}/fakesession.js +0 -0
@@ -0,0 +1,162 @@
1
+ import { format } from 'util';
2
+ import { Proxies } from '../../support/proxies.js';
3
+ import { Logging } from '@google-cloud/logging';
4
+
5
+ // --- Cloud Logging Integration ---
6
+
7
+ // Cloud Logging client instance.
8
+ export let cloudLog;
9
+
10
+ /**
11
+ * @class
12
+ * @implements {GoogleAppsScript.Base.Logger}
13
+ */
14
+ export class FakeLogger {
15
+ constructor() {
16
+ /**
17
+ * @private
18
+ * @type {string[]}
19
+ */
20
+ this.__log = [];
21
+ /**
22
+ * @private
23
+ * @type {string}
24
+ */
25
+ this.__destination = (process.env.LOG_DESTINATION || 'CONSOLE').toUpperCase();
26
+ }
27
+
28
+ /**
29
+ * Clears the log.
30
+ */
31
+ clear() {
32
+ this.__log = [];
33
+ }
34
+
35
+ /**
36
+ * Returns the log contents.
37
+ * @returns {string}
38
+ */
39
+ getLog() {
40
+ return this.__log.join('\n');
41
+ }
42
+
43
+ /**
44
+ * Logs a message.
45
+ * @param {string} formatOrData
46
+ * @param {...any} values
47
+ * @returns {FakeLogger}
48
+ */
49
+ log(formatOrData, ...values) {
50
+ // Check the instance's destination property.
51
+ if (this.__destination === 'NONE') {
52
+ // Still push to the internal log for getLog(), but don't output.
53
+ const message = format(formatOrData, ...values);
54
+ this.__log.push(message);
55
+ return this;
56
+ }
57
+
58
+ const message = format(formatOrData, ...values);
59
+ this.__log.push(message);
60
+
61
+ // Pass the destination and instance context to the writer function.
62
+ writeToCloudOrConsole(message, this);
63
+
64
+ return this;
65
+ }
66
+
67
+ /**
68
+ * Returns 'Logger'.
69
+ * @returns {string}
70
+ */
71
+ toString() {
72
+ return 'Logger';
73
+ }
74
+
75
+ /**
76
+ * Sets the logging destination dynamically.
77
+ * @param {string} destination - The destination ('CONSOLE', 'CLOUD', 'BOTH', 'NONE').
78
+ * @returns {FakeLogger}
79
+ */
80
+ __setLogDestination(destination) {
81
+ if (['CONSOLE', 'CLOUD', 'BOTH', 'NONE'].includes(destination.toUpperCase())) {
82
+ this.__destination = destination.toUpperCase();
83
+ }
84
+ return this;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @returns {FakeLogger}
90
+ */
91
+ export const newFakeLogger = (...args) => Proxies.guard(new FakeLogger(...args));
92
+
93
+ /**
94
+ * Writes a log message to the configured destination (Cloud Logging or console).
95
+ * @param {string} message The log message.
96
+ * @private
97
+ * @param {FakeLogger} loggerInstance The logger instance, to access its destination.
98
+ */
99
+ const writeToCloudOrConsole = (message, loggerInstance) => {
100
+ const logDestination = loggerInstance.__destination;
101
+ const useConsoleLogging = logDestination === 'CONSOLE' || logDestination === 'BOTH';
102
+ const useCloudLogging = logDestination === 'CLOUD' || logDestination === 'BOTH';
103
+
104
+ // Lazy-initialize the cloud logger if needed.
105
+ const initializeCloudLogging = () => {
106
+ if (cloudLog) return; // Already initialized.
107
+ try {
108
+ const projectId = ScriptApp.__projectId;
109
+ if (!projectId) {
110
+ throw new Error('Could not determine Google Cloud Project ID for logging.');
111
+ }
112
+ const logging = new Logging({ projectId });
113
+ const logName = 'gas-fakes/console_logs';
114
+ cloudLog = logging.log(logName);
115
+ console.info(`gas-fakes: Cloud Logging is enabled, writing to log "${logName}".`);
116
+ } catch (err) {
117
+ console.warn(
118
+ `gas-fakes: Cloud Logging failed to initialize. ` +
119
+ 'Falling back to console.log.',
120
+ err.message
121
+ );
122
+ cloudLog = null;
123
+ }
124
+ };
125
+
126
+ // Write to console if CONSOLE or BOTH is specified.
127
+ if (useConsoleLogging) {
128
+ // Write structured JSON to stdout for agent-based collection or local viewing.
129
+ const logEntry = {
130
+ message: message,
131
+ severity: 'INFO',
132
+ };
133
+ console.log(JSON.stringify(logEntry));
134
+ }
135
+
136
+ // Write to Cloud Logging if CLOUD or BOTH is specified and client is ready.
137
+ if (useCloudLogging) {
138
+ // Initialize the cloud logger on the first use if it hasn't been already.
139
+ if (!cloudLog) {
140
+ initializeCloudLogging();
141
+ }
142
+
143
+ const metadata = {
144
+ // Use the 'global' resource type, which is a generic fallback for applications
145
+ // not running on a specific GCP compute service.
146
+ resource: { type: 'global' },
147
+ labels: {
148
+ 'gas-fakes-scriptId': ScriptApp.getScriptId(),
149
+ 'gas-fakes-userId': ScriptApp.__userId,
150
+ },
151
+ jsonPayload: { message: message },
152
+ severity: 'INFO',
153
+ };
154
+
155
+ // Use log.write which returns a promise. T
156
+ // his allows us to catch errors, such as a disabled API or permission issues.
157
+ // however we are firing and forgetting so an error might appear later on in execution
158
+ if (cloudLog) {
159
+ cloudLog.write(cloudLog.entry(metadata)).catch(err=> console.error('gas-fakes: Failed to write to Cloud Logging.', err.message))
160
+ }
161
+ }
162
+ };
@@ -110,12 +110,18 @@ if (typeof globalThis[name] === typeof undefined) {
110
110
 
111
111
  // if it hasn't been intialized yet then do that
112
112
  if (!_app) {
113
- console.log('...activating proxy for', name)
113
+
114
114
  _app = {
115
115
  getOAuthToken,
116
116
  requireAllScopes,
117
117
  requireScopes,
118
118
  getScriptId: Auth.getScriptId,
119
+ get __projectId () {
120
+ return Auth.getProjectId()
121
+ },
122
+ get __userId () {
123
+ return Auth.getUserId()
124
+ },
119
125
  AuthMode: {
120
126
  FULL: 'FULL'
121
127
  },
@@ -167,7 +167,7 @@ class FakeBehavior {
167
167
  this.__idWhitelist = null
168
168
 
169
169
  // individually settable services
170
- const services = ['DocumentApp', 'DriveApp', 'SpreadsheetApp', 'SlidesApp', 'Docs', 'Sheets', 'Drive', 'Slides', 'FormApp', 'Forms']
170
+ const services = ScriptApp.__registeredServices
171
171
  this.__sandboxService = {}
172
172
  services.forEach(f => this.__sandboxService[f] = newFakeSandboxService(this, f))
173
173
 
@@ -0,0 +1,10 @@
1
+
2
+ /**
3
+ * the idea here is to create an empty global entry for the singleton
4
+ * but only load it when it is actually used.
5
+ */
6
+
7
+ import { lazyLoaderApp } from '../common/lazyloader.js'
8
+ import { newFakeSession as maker} from './fakesession.js'
9
+ let _app = null;
10
+ _app = lazyLoaderApp(_app, 'Session', maker)
@@ -1,44 +1,9 @@
1
1
  /**
2
- * the idea here is to create a global entry for the singleton
3
- * before we actually have everything we need to create it.
4
- * We do this by using a proxy, intercepting calls to the
5
- * initial sigleton and diverting them to a completed one
2
+ * the idea here is to create an empty global entry for the singleton
3
+ * but only load it when it is actually used.
6
4
  */
7
- import { newFakeSlidesApp } from './fakeslidesapp.js';
8
- import { Proxies } from '../../support/proxies.js';
9
5
 
6
+ import { lazyLoaderApp } from '../common/lazyloader.js'
7
+ import { newFakeSlidesApp as maker} from './fakeslidesapp.js';
10
8
  let _app = null;
11
-
12
- const name = 'SlidesApp';
13
- const serviceName = 'SlidesApp';
14
-
15
- if (typeof globalThis[name] === typeof undefined) {
16
- const getApp = () => {
17
- if (!_app) {
18
- const realApp = newFakeSlidesApp();
19
-
20
- _app = new Proxy(realApp, {
21
- get(target, prop, receiver) {
22
- if (prop === 'toString') {
23
- return () => name;
24
- }
25
-
26
- const serviceBehavior = ScriptApp.__behavior.sandboxService[serviceName];
27
-
28
- if (serviceBehavior && !serviceBehavior.enabled) {
29
- throw new Error(`${name} service is disabled by sandbox settings.`);
30
- }
31
-
32
- const allowedMethods = serviceBehavior?.methods;
33
- if (allowedMethods && typeof target[prop] === 'function' && !allowedMethods.includes(prop)) {
34
- throw new Error(`Method ${name}.${prop} is not allowed by sandbox settings.`);
35
- }
36
-
37
- return Reflect.get(target, prop, receiver);
38
- },
39
- });
40
- }
41
- return _app;
42
- };
43
- Proxies.registerProxy(name, getApp);
44
- }
9
+ _app = lazyLoaderApp(_app, 'SlidesApp', maker)
@@ -1,53 +1,9 @@
1
- // fake Apps Script SpreadsheetApp
2
- /**
3
- * the idea here is to create a global entry for the singleton
4
- * before we actually have everything we need to create it.
5
- * We do this by using a proxy, intercepting calls to the
6
- * initial sigleton and diverting them to a completed one
7
- */
8
- import { newFakeSpreadsheetApp } from './fakespreadsheetapp.js'
9
- import { Proxies } from '../../support/proxies.js'
10
-
11
- // This will eventually hold a proxy for DriveApp
12
- let _app = null;
13
1
 
14
2
  /**
15
- * adds to global space to mimic Apps Script behavior
3
+ * the idea here is to create an empty global entry for the singleton
4
+ * but only load it when it is actually used.
16
5
  */
17
- const name = "SpreadsheetApp";
18
- const serviceName = "SpreadsheetApp";
19
-
20
- if (typeof globalThis[name] === typeof undefined) {
21
- const getApp = () => {
22
- // if it hasnt been intialized yet then do that
23
- if (!_app) {
24
- const realApp = newFakeSpreadsheetApp();
25
-
26
- _app = new Proxy(realApp, {
27
- get(target, prop, receiver) {
28
- // toString on the proxy target itself
29
- if (prop === 'toString') {
30
- return () => name;
31
- }
32
-
33
- const serviceBehavior = ScriptApp.__behavior.sandboxService[serviceName];
34
-
35
- if (!serviceBehavior.enabled) {
36
- throw new Error(`${name} service is disabled by sandbox settings.`);
37
- }
38
-
39
- const allowedMethods = serviceBehavior.methods;
40
- if (allowedMethods && typeof target[prop] === 'function' && !allowedMethods.includes(prop)) {
41
- throw new Error(`Method ${name}.${prop} is not allowed by sandbox settings.`);
42
- }
43
-
44
- return Reflect.get(...arguments);
45
- }
46
- });
47
- }
48
- // this is the actual driveApp we'll return from the proxy
49
- return _app
50
- }
51
-
52
- Proxies.registerProxy(name, getApp)
53
- }
6
+ import { lazyLoaderApp } from '../common/lazyloader.js'
7
+ import { newFakeSpreadsheetApp as maker} from './fakespreadsheetapp.js'
8
+ let _app = null;
9
+ _app = lazyLoaderApp(_app, 'SpreadsheetApp', maker)
@@ -11,11 +11,9 @@ import { newFakeDeveloperMetadataFinder } from "./fakedevelopermetadatafinder.js
11
11
  import { newFakeSheetRangeList } from "./fakesheetrangelist.js";
12
12
  import { FakeTextFinder, newFakeTextFinder } from "./faketextfinder.js";
13
13
 
14
- import { newFakeNamedRange } from "./fakenamedrange.js";
14
+ import { newFakeNamedRange } from "./fakenamedrange.js";
15
15
  import { newFakeProtection } from "./fakeprotection.js";
16
16
 
17
-
18
-
19
17
  const { is, isEnum } = Utils;
20
18
 
21
19
  export const newFakeSheet = (properties, parent) => {
@@ -1009,7 +1007,8 @@ export class FakeSheet {
1009
1007
  ],
1010
1008
  };
1011
1009
  this.__batchUpdate(obj);
1012
- return this.getParent().getSheetByName(name);
1010
+ this.__properties.title = name;
1011
+ return this;
1013
1012
  }
1014
1013
 
1015
1014
  protect() {
@@ -26,7 +26,6 @@ const registerApp = (_app, name, kind) => {
26
26
  const getApp = () => {
27
27
  // if it hasnt been intialized yet then do that
28
28
  if (!_app) {
29
- console.log('...activating proxy for', name)
30
29
  _app = newFakeService(kind)
31
30
  }
32
31
  // this is the actual driveApp we'll return from the proxy
@@ -103,7 +103,6 @@ if (typeof globalThis[name] === typeof undefined) {
103
103
  const getApp = () => {
104
104
  // if it hasne been intialized yet then do that
105
105
  if (!_app) {
106
- console.log('...activating proxy for', name)
107
106
  _app = {
108
107
  fetch
109
108
  }
@@ -1,23 +1,9 @@
1
- import { Proxies } from '../../support/proxies.js'
2
- import { newFakeUtilities } from './fakeutilities.js';
3
-
4
-
5
- // This will eventually hold a proxy for Utilities
6
- let _app = null
7
1
 
8
2
  /**
9
- * adds to global space to mimic Apps Script behavior
3
+ * the idea here is to create an empty global entry for the singleton
4
+ * but only load it when it is actually used.
10
5
  */
11
- const name = "Utilities"
12
- if (typeof globalThis[name] === typeof undefined) {
13
- const getApp = () => {
14
- // if it hasnt been intialized yet then do that
15
- if (!_app) {
16
- console.log('...activating proxy for', name)
17
- _app = newFakeUtilities()
18
- }
19
- // this is the actual driveApp we'll return from the proxy
20
- return _app
21
- }
22
- Proxies.registerProxy(name, getApp)
23
- }
6
+ import { lazyLoaderApp } from '../common/lazyloader.js'
7
+ import { newFakeUtilities as maker } from './fakeutilities.js';
8
+ let _app = null;
9
+ _app = lazyLoaderApp(_app, 'Utilities', maker)
@@ -1,6 +1,7 @@
1
1
  import { Utils } from './utils.js';
2
2
 
3
3
  const serviceRegistry = new Set();
4
+ const loadedRegistry = new Set();
4
5
 
5
6
  /**
6
7
  * diverts the property get to another object returned by the getApp function
@@ -11,10 +12,24 @@ const getAppHandler = (getApp, name) => {
11
12
  return {
12
13
 
13
14
  get(_, prop, receiver) {
15
+
14
16
  // this will let the caller know we're not really running in Apps Script
17
+ // every service can return this
15
18
  if (prop === 'isFake') return true;
16
19
 
20
+ // this returns all services already registered - may be useful
21
+ if (prop === '__registeredServices') return Array.from(serviceRegistry)
22
+
23
+ // this returns all services already loaded - may be useful
24
+ if (prop === '__loadedServices') return Array.from(loadedRegistry)
25
+
26
+ // this will get the app and lazy load it if its not already loaded
17
27
  const app = getApp();
28
+
29
+ // now we have a loaded service
30
+ loadedRegistry.add(name)
31
+
32
+ // are we being asked to run a method?
18
33
  const member = Reflect.get(app, prop, receiver);
19
34
 
20
35
  // Check method whitelist if it's a function call on a service
@@ -37,6 +52,7 @@ const getAppHandler = (getApp, name) => {
37
52
  }
38
53
  }
39
54
 
55
+ // keep a note of which services have been registered
40
56
  const registerProxy = (name, getApp) => {
41
57
  serviceRegistry.add(name);
42
58
  const value = new Proxy({}, getAppHandler(getApp, name))
@@ -274,9 +274,9 @@ const fxInit = ({
274
274
  } = synced
275
275
 
276
276
  // set these values from the subprocess for the main project
277
- //Auth.setProjectId(projectId)
277
+ Auth.setProjectId(projectId)
278
278
  //Auth.setAuth(scopes)
279
- //Auth.setTokenInfo(tokenInfo)
279
+ Auth.setTokenInfo(tokenInfo)
280
280
  //Auth.setAdcPath(adcPath)
281
281
  //Auth.setAccessToken(accessToken)
282
282
  Auth.setSettings(settings)
@@ -1,33 +0,0 @@
1
-
2
- /**
3
- * fake Apps Script Session
4
- * the idea here is to create a global entry for the singleton
5
- * before we actually have everything we need to create it.
6
- * We do this by using a proxy, intercepting calls to the
7
- * initial sigleton and diverting them to a completed one
8
- */
9
-
10
- import { Proxies } from '../../support/proxies.js'
11
- import { newFakeSession } from './fakesession.js'
12
-
13
- let _app = null
14
-
15
- /**
16
- * adds to global space to mimic Apps Script behavior
17
- */
18
- const name = "Session"
19
- if (typeof globalThis[name] === typeof undefined) {
20
-
21
- const getApp = () => {
22
- // if it hasn't been intialized yet then do that
23
- if (!_app) {
24
- console.log('...activating proxy for', name)
25
- _app = newFakeSession()
26
- }
27
- // this is the actual driveApp we'll return from the proxy
28
- return _app
29
- }
30
-
31
- Proxies.registerProxy(name, getApp)
32
-
33
- }
File without changes