@mcpher/gas-fakes 1.0.19 → 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 (72) hide show
  1. package/README.md +63 -30
  2. package/gasmess/bruce/pbx.js +53 -2
  3. package/gprompts/gas-inventory.js +92 -0
  4. package/gprompts/gas-inventory.json +176 -0
  5. package/gprompts/inventory-list.json +1 -0
  6. package/gprompts/model.json +34 -0
  7. package/gprompts/package-lock.json +1103 -0
  8. package/gprompts/package.json +18 -0
  9. package/gprompts/temp_fetch.mjs +9 -0
  10. package/gprompts/update-progress.js +142 -0
  11. package/package.json +6 -2
  12. package/setup.sh +147 -0
  13. package/src/index.js +9 -2
  14. package/src/services/advdocs/app.js +4 -23
  15. package/src/services/advdrive/app.js +6 -28
  16. package/src/services/advforms/app.js +6 -25
  17. package/src/services/advgmail/app.js +11 -0
  18. package/src/services/advgmail/fakeadvgmail.js +39 -0
  19. package/src/services/advgmail/fakeadvgmaillabels.js +119 -0
  20. package/src/services/advgmail/fakeadvgmailusers.js +23 -0
  21. package/src/services/advgmail/gmailapis.js +15 -0
  22. package/src/services/advsheets/app.js +6 -26
  23. package/src/services/advslides/app.js +6 -28
  24. package/src/services/common/lazyloader.js +22 -0
  25. package/src/services/documentapp/app.js +8 -42
  26. package/src/services/documentapp/appenderhelpers.js +21 -23
  27. package/src/services/documentapp/elementhelpers.js +0 -1
  28. package/src/services/documentapp/elementoptions.js +22 -32
  29. package/src/services/documentapp/fakeelement.js +20 -3
  30. package/src/services/documentapp/fakelistitem.js +177 -28
  31. package/src/services/documentapp/fakeparagraph.js +194 -7
  32. package/src/services/documentapp/faketable.js +16 -0
  33. package/src/services/documentapp/faketablerow.js +15 -0
  34. package/src/services/documentapp/nrhelpers.js +1 -0
  35. package/src/services/documentapp/shadowdocument.js +10 -0
  36. package/src/services/driveapp/app.js +6 -28
  37. package/src/services/enums/gmailenums.js +8 -0
  38. package/src/services/formapp/app.js +5 -40
  39. package/src/services/gmailapp/app.js +11 -0
  40. package/src/services/gmailapp/fakegmailapp.js +35 -0
  41. package/src/services/gmailapp/fakegmaillabel.js +44 -0
  42. package/src/services/logger/app.js +8 -0
  43. package/src/services/logger/fakelogger.js +162 -0
  44. package/src/services/scriptapp/app.js +7 -1
  45. package/src/services/scriptapp/behavior.js +1 -1
  46. package/src/services/session/app.js +10 -0
  47. package/src/services/slidesapp/app.js +5 -40
  48. package/src/services/spreadsheetapp/app.js +6 -50
  49. package/src/services/spreadsheetapp/fakeprotection.js +6 -7
  50. package/src/services/spreadsheetapp/fakesheet.js +3 -4
  51. package/src/services/stores/app.js +0 -1
  52. package/src/services/urlfetchapp/app.js +0 -1
  53. package/src/services/utilities/app.js +6 -20
  54. package/src/support/gmailcacher.js +7 -0
  55. package/src/support/helpers.js +2 -2
  56. package/src/support/proxies.js +20 -1
  57. package/src/support/sxgmail.js +55 -0
  58. package/src/support/syncit.js +5 -2
  59. package/src/support/utils.js +46 -15
  60. package/src/support/workersync/sxfunctions.js +5 -10
  61. package/togas.bash +18 -5
  62. package/ghissues/image-size-inconsistency-issue.sh +0 -46
  63. package/ghissues/issue-formapp-create-title-inconsistency.sh +0 -51
  64. package/ghissues/issue-positioned-image.sh +0 -25
  65. package/ghissues/post-issue.sh +0 -53
  66. package/ghissues/protection-editors-issue.sh +0 -33
  67. package/ghissues/review-sandbox-listing-issue.sh +0 -45
  68. package/ghissues/sandbox-issue.sh +0 -31
  69. package/ghissues/setup-under-construction.sh +0 -107
  70. package/src/services/base/app.js +0 -33
  71. /package/{regenerate-progress-reports.sh → gprompts/regenerate-progress-reports.sh} +0 -0
  72. /package/src/services/{base → session}/fakesession.js +0 -0
@@ -0,0 +1,23 @@
1
+ import { FakeAdvResource } from '../common/fakeadvresource.js';
2
+ import { Syncit } from '../../support/syncit.js';
3
+ import { signatureArgs, ssError, gError } from '../../support/helpers.js';
4
+ import { newFakeAdvGmailLabels } from './fakeadvgmaillabels.js';
5
+
6
+ import { Proxies } from '../../support/proxies.js';
7
+ import { Utils } from '../../support/utils.js';
8
+ const { is } = Utils
9
+
10
+ export const newFakeAdvGmailUsers = (...args) => Proxies.guard(new FakeAdvGmailUsers(...args))
11
+
12
+ class FakeAdvGmailUsers extends FakeAdvResource {
13
+ constructor(mainService) {
14
+ super(mainService, 'gmail', Syncit.fxGmail);
15
+ this.gmail = mainService;
16
+ this.__fakeObjectType = 'Gmail.Users';
17
+ }
18
+
19
+ get Labels() {
20
+ return newFakeAdvGmailLabels(this.gmail);
21
+ }
22
+
23
+ }
@@ -0,0 +1,15 @@
1
+ import { google } from "googleapis";
2
+ import { Auth } from '../../support/auth.js'
3
+ import { syncLog} from '../../support/workersync/synclogger.js'
4
+
5
+ let __client = null;
6
+
7
+ export const getGmailApiClient = () => {
8
+ const auth = Auth.getAuth()
9
+ if (!__client) {
10
+ syncLog('Creating new Gmail API client');
11
+ __client = google.gmail({ version: 'v1', auth });
12
+ }
13
+ return __client;
14
+ }
15
+
@@ -1,32 +1,12 @@
1
1
 
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 { newFakeAdvSheets } from './fakeadvsheets.js'
9
- import { Proxies } from '../../support/proxies.js'
10
-
11
-
12
- let _app = null
13
2
 
14
3
  /**
15
- * adds to global space to mimic Apps Script behavior
4
+ * the idea here is to create an empty global entry for the singleton
5
+ * but only load it when it is actually used.
16
6
  */
17
- const name = "Sheets"
18
- if (typeof globalThis[name] === typeof undefined) {
19
-
20
- const getApp = () => {
21
- // if it hasne been intialized yet then do that
22
- if (!_app) {
23
- console.log('...activating proxy for', name)
24
- _app = newFakeAdvSheets()
25
- }
26
- // this is the actual driveApp we'll return from the proxy
27
- return _app
28
- }
29
7
 
30
- Proxies.registerProxy(name, getApp)
8
+ import { newFakeAdvSheets as maker} from './fakeadvsheets.js'
9
+ import { lazyLoaderApp } from '../common/lazyloader.js'
31
10
 
32
- }
11
+ let _app = null;
12
+ _app = lazyLoaderApp(_app, 'Sheets', maker)
@@ -1,32 +1,10 @@
1
1
 
2
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
3
+ * the idea here is to create an empty global entry for the singleton
4
+ * but only load it when it is actually used.
7
5
  */
8
- import { newFakeAdvSlides } from './fakeadvslides.js'
9
- import { Proxies } from '../../support/proxies.js'
6
+ import { newFakeAdvSlides as maker } from './fakeadvslides.js'
7
+ import { lazyLoaderApp } from '../common/lazyloader.js'
10
8
 
11
- // This will eventually hold a proxy for slidesapp
12
- let _app = null
13
-
14
- /**
15
- * adds to global space to mimic Apps Script behavior
16
- */
17
- const name = "Slides"
18
- if (typeof globalThis[name] === typeof undefined) {
19
-
20
- const getApp = () => {
21
- // if it hasne been intialized yet then do that
22
- if (!_app) {
23
- console.log('...activating proxy for', name)
24
- _app = newFakeAdvSlides()
25
- }
26
- // this is the actual driveApp we'll return from the proxy
27
- return _app
28
- }
29
-
30
- Proxies.registerProxy(name, getApp)
31
-
32
- }
9
+ let _app = null;
10
+ _app = lazyLoaderApp(_app, 'Slides', maker)
@@ -0,0 +1,22 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+
3
+ export const lazyLoaderApp = (app, name, maker) => {
4
+
5
+ if (typeof globalThis[name] === typeof undefined) {
6
+
7
+ const getApp = () => {
8
+
9
+ // if it hasne been intialized yet then do that
10
+ if (!app) {
11
+ //console.log ('...loading', name)
12
+ app = maker()
13
+ }
14
+ // this is the actual driveApp we'll return from the proxy
15
+ return app
16
+ }
17
+ //console.log ('...registering', name)
18
+ Proxies.registerProxy(name, getApp)
19
+
20
+ }
21
+ return app
22
+ }
@@ -1,47 +1,13 @@
1
+
2
+
1
3
  /**
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.
6
- * We also need to make sure all element types are registered.
4
+ * the idea here is to create an empty global entry for the singleton
5
+ * but only load it when it is actually used.
7
6
  */
8
- import { newFakeDocumentApp } from './fakedocumentapp.js';
9
- import { Proxies } from '../../support/proxies.js';
7
+
8
+ import { lazyLoaderApp } from '../common/lazyloader.js'
9
+ import { newFakeDocumentApp as maker } from './fakedocumentapp.js';
10
10
  import './elements.js'; // This ensures all element types register themselves before DocumentApp is used.
11
11
 
12
12
  let _app = null;
13
-
14
- const name = 'DocumentApp';
15
- const serviceName = 'DocumentApp';
16
-
17
- if (typeof globalThis[name] === typeof undefined) {
18
- // By importing this, we ensure all element types register themselves.
19
- const getApp = () => {
20
- if (!_app) {
21
- const realApp = newFakeDocumentApp();
22
-
23
- _app = new Proxy(realApp, {
24
- get(target, prop, receiver) {
25
- if (prop === 'toString') {
26
- return () => name;
27
- }
28
-
29
- const serviceBehavior = ScriptApp.__behavior.sandboxService[serviceName];
30
-
31
- if (!serviceBehavior.enabled) {
32
- throw new Error(`${name} service is disabled by sandbox settings.`);
33
- }
34
-
35
- const allowedMethods = serviceBehavior.methods;
36
- if (allowedMethods && typeof target[prop] === 'function' && !allowedMethods.includes(prop)) {
37
- throw new Error(`Method ${name}.${prop} is not allowed by sandbox settings.`);
38
- }
39
-
40
- return Reflect.get(...arguments);
41
- },
42
- });
43
- }
44
- return _app;
45
- };
46
- Proxies.registerProxy(name, getApp);
47
- }
13
+ _app = lazyLoaderApp(_app, 'DocumentApp', maker)
@@ -1,6 +1,6 @@
1
1
  import { Utils } from "../../support/utils.js";
2
2
  import { ElementType } from '../enums/docsenums.js';
3
- const { is, isBlob } = Utils
3
+ const { is, isBlob , stringCircular, lobify} = Utils
4
4
  import { getElementFactory } from './elementRegistry.js'
5
5
  import { signatureArgs, notYetImplemented } from '../../support/helpers.js';
6
6
  import { findItem } from './elementhelpers.js';
@@ -121,7 +121,7 @@ const calculateInsertionPointsAndInitialRequests = (self, childIndex, isAppend,
121
121
  if (isParaInsert) {
122
122
  // Inserting into an existing paragraph. No newlines needed.
123
123
  if (childIndex < 0 || childIndex > children.length) {
124
- throw new Error(`Child index (${childIndex}) must be between 0 and the number of child elements (${children.length}).`);
124
+ throw new Error(`Child index (${childIndex}) must be less than or equal to the number of child elements (${children.length}).`);
125
125
  }
126
126
  if (children.length === 0 || childIndex === children.length) {
127
127
  // Inserting into an empty paragraph or at the end.
@@ -136,23 +136,29 @@ const calculateInsertionPointsAndInitialRequests = (self, childIndex, isAppend,
136
136
  childStartIndex = insertIndex; // The new child will start where we insert it.
137
137
  } else {
138
138
  // It's an insert operation, creating a new element in a Body/Header/etc.
139
- if (childIndex < 0 || childIndex >= children.length) {
139
+ if (childIndex < 0 || childIndex > children.length) {
140
140
  throw new Error(`Child index (${childIndex}) must be less than or equal to the number of child elements (${children.length}).`);
141
141
  }
142
- const targetChildTwig = children[childIndex];
143
- const targetChildItem = elementMap.get(targetChildTwig.name);
142
+ // Handle the case where we are inserting at the end (which is like an append).
143
+ if (childIndex === children.length) {
144
+ const lastChildTwig = children.length > 0 ? children[children.length - 1] : null;
145
+ const lastChildItem = lastChildTwig ? elementMap.get(lastChildTwig.name) : item;
146
+ insertIndex = lastChildItem.endIndex - 1;
147
+ newElementStartIndex = lastChildItem.endIndex;
148
+ leading = '\n';
149
+ } else {
150
+ const targetChildTwig = children[childIndex];
151
+ const targetChildItem = elementMap.get(targetChildTwig.name);
144
152
 
145
- if (targetChildItem.__type === "TABLE") {
146
- insertIndex = targetChildItem.startIndex - 1; // Insert before the table.
147
- if (childIndex < 1) {
148
- throw new Error(`Cannot insert before the first child element table (${targetChildItem.name})`);
153
+ if (targetChildItem.__type === "TABLE") {
154
+ insertIndex = targetChildItem.startIndex - 1; // Insert before the table.
155
+ leading = '\n';
156
+ newElementStartIndex = insertIndex + 1;
157
+ } else {
158
+ insertIndex = targetChildItem.startIndex;
159
+ trailing = '\n';
160
+ newElementStartIndex = insertIndex;
149
161
  }
150
- leading = '\n'
151
- newElementStartIndex = insertIndex + 1;
152
- } else {
153
- insertIndex = targetChildItem.startIndex
154
- trailing = '\n'
155
- newElementStartIndex = insertIndex;
156
162
  }
157
163
  childStartIndex = null; // Child start index is unknown, must be found within container.
158
164
  }
@@ -301,14 +307,6 @@ const elementInserter = (self, elementOrText, childIndex, options) => {
301
307
  }
302
308
  }
303
309
 
304
- // After the refresh, the 'self' object is stale. We need to find its new representation
305
- // in the updated element map and update its internal name. This "revives" the object
306
- // for subsequent method calls in the user's script.
307
- const newSelfItem = findItem(shadow.elementMap, selfType, selfStartIndex, segmentId);
308
- if (newSelfItem && newSelfItem.__name !== selfName) {
309
- self.__name = newSelfItem.__name;
310
- }
311
-
312
310
  // 6. Handle table content population if necessary. This is a two-phase update
313
311
  // because we need the table to exist before we can get the indices to populate its cells.
314
312
  if (options.elementType === ElementType.TABLE) {
@@ -285,5 +285,4 @@ export const updateParagraphStyle = (element, paragraphStyle, fields) => {
285
285
  Docs.Documents.batchUpdate({ requests }, shadow.getId());
286
286
  shadow.refresh();
287
287
  return element;
288
-
289
288
  };
@@ -381,11 +381,13 @@ export const imageOptions = {
381
381
  elementType: ElementType.INLINE_IMAGE,
382
382
  insertMethodSignature: 'DocumentApp.Body.insertImage',
383
383
  canAcceptText: false,
384
- getMainRequest: ({ content: image, location, isAppend, self, leading, trailing }) => {
384
+ // This internal helper is extracted so it can be used by Paragraph.appendInlineImage
385
+ // without invoking the complex request-building logic of getMainRequest.
386
+ __getImageUriAndSize: (image) => {
385
387
  const isDetachedImage = is.object(image) && is.function(image.getType) && image.getType() === ElementType.INLINE_IMAGE;
386
388
  const isBlobSource = isBlob(image);
387
389
 
388
- let uri, size, fileId;
390
+ let uri, size, fileId, cleanup = null;
389
391
 
390
392
  if (isDetachedImage) {
391
393
  if (!image.__isDetached) throw new Error('Element must be detached.');
@@ -446,13 +448,14 @@ export const imageOptions = {
446
448
  }
447
449
  uri = fileWithLink.webContentLink;
448
450
 
449
-
450
- // We can get the byte size, but not the height/width for a blob.
451
- // The API seems to require an objectSize, so we'll provide a default.
452
- // This is the desired size in the document, not the intrinsic image size.
453
451
  size = {
454
- height: { magnitude: 100, unit: 'PT' },
455
- width: { magnitude: 100, unit: 'PT' },
452
+ height: { magnitude: image.getHeight ? image.getHeight() : 100, unit: 'PT' },
453
+ width: { magnitude: image.getWidth ? image.getWidth() : 100, unit: 'PT' },
454
+ };
455
+
456
+ cleanup = () => {
457
+ try { Drive.Files.update({ trashed: true }, fileId); }
458
+ catch (e) { console.warn(`Failed to cleanup temporary image file ${fileId}:`, e.message); }
456
459
  };
457
460
  } catch (e) {
458
461
  // if something fails during setup, trash the file immediately
@@ -466,9 +469,15 @@ export const imageOptions = {
466
469
  } else {
467
470
  throw new Error('Only inserting a copied (detached) InlineImage or a Blob is supported at this time.');
468
471
  }
472
+ return { uri, size, cleanup };
473
+ },
474
+ getMainRequest: ({ content: image, location, isAppend, self, leading, trailing }) => {
475
+ const { uri, size, cleanup } = imageOptions.__getImageUriAndSize(image);
476
+
469
477
  if (!uri) {
470
478
  throw new Error('Could not determine image URI for insertion.');
471
479
  }
480
+
472
481
  const imageRequest = {
473
482
  insertInlineImage: {
474
483
  uri,
@@ -480,32 +489,13 @@ export const imageOptions = {
480
489
  imageRequest.insertInlineImage.objectSize = size;
481
490
  }
482
491
 
483
- let finalRequests;
484
- if (leading) { // Append case
485
- const textRequest = { insertText: { text: leading, location } }; // leading is '\n'
486
- // The image needs to be inserted into the new paragraph created by the leading newline.
487
- // The new paragraph will start at location.index + 1.
488
- const imageLocation = { ...location, index: location.index + 1 };
489
- const imageRequestWithCorrectedLocation = { ...imageRequest, insertInlineImage: { ...imageRequest.insertInlineImage, location: imageLocation } };
490
- finalRequests = [textRequest, imageRequestWithCorrectedLocation];
491
- } else if (trailing) { // Insert case
492
- const textRequest = { insertText: { text: trailing, location } };
493
- // Insert a newline, then the image at the same location. The API inserts the image after the newline.
494
- finalRequests = [textRequest, imageRequest];
495
- } else {
496
- // This case is for inserting into an existing paragraph (e.g., Paragraph.appendImage).
497
- finalRequests = [imageRequest];
498
- }
492
+ const textRequest = (leading || trailing) ? { insertText: { text: leading || trailing, location } } : null;
493
+ const imageLocation = (leading) ? { ...location, index: location.index + 1 } : location;
494
+ const imageRequestWithCorrectedLocation = { ...imageRequest, insertInlineImage: { ...imageRequest.insertInlineImage, location: imageLocation } };
499
495
 
500
- if (isBlobSource) {
501
- const cleanup = () => {
502
- try { Drive.Files.update({ trashed: true }, fileId); }
503
- catch (e) { console.warn(`Failed to cleanup temporary image file ${fileId}:`, e.message); }
504
- };
505
- return { requests: finalRequests, cleanup };
506
- }
496
+ const finalRequests = textRequest ? [textRequest, imageRequestWithCorrectedLocation] : [imageRequestWithCorrectedLocation];
507
497
 
508
- return finalRequests;
498
+ return { requests: finalRequests, cleanup };
509
499
  },
510
500
  getStyleRequests: null, // Styling is part of the image object itself.
511
501
  findType: 'PARAGRAPH', // We are creating a paragraph to hold the image.
@@ -7,6 +7,7 @@ import { Utils } from '../../support/utils.js';
7
7
  const { is } = Utils;
8
8
  import { ElementType } from '../enums/docsenums.js';
9
9
  import { getElementFactory } from './elementRegistry.js';
10
+ import { findItem } from './elementhelpers.js';
10
11
 
11
12
  /**
12
13
  * @typedef {import('./shadow.js').ShadowStructure} ShadowStructure
@@ -71,7 +72,11 @@ export class FakeElement {
71
72
  throw new Error(`Invalid arguments for attached FakeElement: ${nameOrItem}. Name must start with '${shadowPrefix}'.`);
72
73
  }
73
74
  this.__isDetached = false;
74
- this.__shadowDocument = shadowDocument;
75
+ this.__shadowDocument = shadowDocument;
76
+ // Cache the element's initial position. This is the key to "reviving" the element.
77
+ const initialItem = this.__getElementMapItem(nameOrItem);
78
+ this.__initialStartIndex = initialItem.startIndex;
79
+ this.__initialSegmentId = initialItem.__segmentId;
75
80
  this.__name = nameOrItem;
76
81
  this.__detachedItem = null;
77
82
  } else if (is.object(nameOrItem)) { // Detached
@@ -105,7 +110,18 @@ export class FakeElement {
105
110
  if (this.__isDetached) {
106
111
  return this.__detachedItem;
107
112
  }
108
- return this.__getElementMapItem(this.__name);
113
+
114
+ // 1. Try the last known name. This is fast if nothing has changed.
115
+ const lastKnownItem = this.__getElementMapItem(this.__name, true); // noThrow = true
116
+ if (lastKnownItem) {
117
+ return lastKnownItem;
118
+ }
119
+
120
+ // 2. If the last name was stale, find the element by its initial position.
121
+ const revivedItem = findItem(this.__shadowDocument.elementMap, this.getType().toString(), this.__initialStartIndex, this.__initialSegmentId);
122
+ // 3. Update the internal name so the next lookup is fast again.
123
+ this.__name = revivedItem.__name;
124
+ return revivedItem;
109
125
  }
110
126
 
111
127
  /**
@@ -114,9 +130,10 @@ export class FakeElement {
114
130
  * @returns {object} The element map item.
115
131
  * @private
116
132
  */
117
- __getElementMapItem(name) {
133
+ __getElementMapItem(name, noThrow = false) {
118
134
  const item = this.__shadowDocument.getElement(name);
119
135
  if (!item) {
136
+ if (noThrow) return null;
120
137
  throw new Error(`element with name ${name} not found`);
121
138
  }
122
139
  return item;