@mcpher/gas-fakes 2.5.2 → 2.5.4

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 (76) hide show
  1. package/README.md +17 -3
  2. package/package.json +1 -1
  3. package/pngs/srv.jpg +0 -0
  4. package/src/cli/app.js +1 -0
  5. package/src/cli/togas.js +23 -14
  6. package/src/index.js +1 -0
  7. package/src/services/advslides/fakeadvslides.js +11 -5
  8. package/src/services/base/app.js +9 -0
  9. package/src/services/base/fakebase.js +28 -0
  10. package/src/services/common/fakeadvresource.js +3 -2
  11. package/src/services/content/contentservice.js +3 -2
  12. package/src/services/enums/baseenums.js +47 -0
  13. package/src/services/enums/contentenums.js +1 -3
  14. package/src/services/enums/scriptenums.js +31 -4
  15. package/src/services/enums/slidesenums.js +3 -1
  16. package/src/services/enums/xmlenums.js +14 -0
  17. package/src/services/html/serverworker.js +1 -1
  18. package/src/services/scriptapp/app.js +14 -7
  19. package/src/services/scriptapp/fakeauthorizationinfo.js +4 -4
  20. package/src/services/slidesapp/app.js +5 -0
  21. package/src/services/slidesapp/fakeautofit.js +1 -1
  22. package/src/services/slidesapp/fakeborder.js +106 -0
  23. package/src/services/slidesapp/fakecolorscheme.js +1 -1
  24. package/src/services/slidesapp/fakefill.js +216 -0
  25. package/src/services/slidesapp/fakegroup.js +35 -0
  26. package/src/services/slidesapp/fakeimage.js +118 -0
  27. package/src/services/slidesapp/fakelayout.js +351 -0
  28. package/src/services/slidesapp/fakeline.js +2 -2
  29. package/src/services/slidesapp/fakelinefill.js +15 -16
  30. package/src/services/slidesapp/fakelink.js +20 -3
  31. package/src/services/slidesapp/fakelist.js +36 -0
  32. package/src/services/slidesapp/fakeliststyle.js +105 -0
  33. package/src/services/slidesapp/fakemaster.js +358 -0
  34. package/src/services/slidesapp/fakenotesmaster.js +125 -0
  35. package/src/services/slidesapp/fakenotespage.js +102 -2
  36. package/src/services/slidesapp/fakepagebackground.js +109 -1
  37. package/src/services/slidesapp/fakepageelement.js +157 -18
  38. package/src/services/slidesapp/fakepageelementrange.js +28 -0
  39. package/src/services/slidesapp/fakepagerange.js +28 -0
  40. package/src/services/slidesapp/fakeparagraphstyle.js +139 -0
  41. package/src/services/slidesapp/fakepicturefill.js +32 -0
  42. package/src/services/slidesapp/fakepresentation.js +126 -2
  43. package/src/services/slidesapp/fakeshape.js +9 -0
  44. package/src/services/slidesapp/fakeslide.js +216 -24
  45. package/src/services/slidesapp/fakesolidfill.js +45 -0
  46. package/src/services/slidesapp/fakespeakerspotlight.js +18 -0
  47. package/src/services/slidesapp/faketable.js +55 -9
  48. package/src/services/slidesapp/faketablecell.js +141 -12
  49. package/src/services/slidesapp/faketablecellrange.js +28 -0
  50. package/src/services/slidesapp/faketablecolumn.js +72 -0
  51. package/src/services/slidesapp/faketablerow.js +31 -0
  52. package/src/services/slidesapp/faketextrange.js +179 -135
  53. package/src/services/slidesapp/faketextstyle.js +158 -0
  54. package/src/services/slidesapp/fakevideo.js +35 -0
  55. package/src/services/slidesapp/fakewordart.js +22 -0
  56. package/src/services/slidesapp/pageelementfactory.js +24 -1
  57. package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +92 -12
  58. package/src/services/spreadsheetapp/fakespreadsheet.js +360 -62
  59. package/src/services/spreadsheetapp/fakespreadsheettheme.js +53 -0
  60. package/src/services/urlfetchapp/app.js +216 -175
  61. package/src/services/xmlservice/app.js +3 -78
  62. package/src/services/xmlservice/fakeattribute.js +15 -0
  63. package/src/services/xmlservice/fakecdata.js +40 -0
  64. package/src/services/xmlservice/fakecomment.js +34 -0
  65. package/src/services/xmlservice/fakecontent.js +51 -0
  66. package/src/services/xmlservice/fakedoctype.js +68 -0
  67. package/src/services/xmlservice/fakedocument.js +110 -13
  68. package/src/services/xmlservice/fakeelement.js +297 -82
  69. package/src/services/xmlservice/fakeentityref.js +54 -0
  70. package/src/services/xmlservice/fakeformat.js +67 -22
  71. package/src/services/xmlservice/fakeprocessinginstruction.js +44 -0
  72. package/src/services/xmlservice/faketext.js +39 -0
  73. package/src/services/xmlservice/fakexmlservice.js +118 -0
  74. package/src/support/sxfetch.js +60 -0
  75. package/tools/omlx.env.example +6 -0
  76. package/tools/omlx_mcp_server.cjs +157 -0
package/README.md CHANGED
@@ -16,7 +16,7 @@ npm i @mcpher/gas-fakes
16
16
 
17
17
  For a complete guide on how to set up your local environment for authentication and development, please see the consolidated guide: [Getting Started with `gas-fakes`](GETTING_STARTED.md)
18
18
 
19
- Collaborators should fork the repo and use the local versions of these files - see collaborators info.
19
+ Collaborators should fork the repo and use the local versions of these files - see [collaborators info](./notes/contributing.md)
20
20
 
21
21
  ### Use exactly the same code as in Apps Script
22
22
  ## Usage
@@ -159,25 +159,39 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
159
159
 
160
160
  ## <img src="../pngs/logo.png" alt="gas-fakes logo" width="50" align="top"> Further Reading
161
161
 
162
+
163
+
162
164
  ## Watch the gas-fakes intro video
163
165
 
164
166
  [![Watch the intro video](./pngs/introvideo.png)](https://youtu.be/oEjpIrkYpEM)
165
167
 
168
+ ## Watch the explainer about delegating work to local LLMs to save token costs
169
+
170
+ [![Use local LLMs to save tokens](./pngs/hybrid_LLM_Architecture_Overview.png)](https://youtu.be/tcvU2NLEaNE)
171
+
166
172
  ## Watch the gf_agent video on natural language automation
167
173
 
168
174
  [![Use natural language with gf_agent](./pngs/gfagent.png)](https://youtu.be/lujByoX71HU)
169
175
 
176
+ ## Watch the local webapps and addons development video
177
+
178
+ [![Local Apps Script Webapp and UI Emulation with gas-fakes](./pngs/srv.jpg)](https://youtu.be/vH9wl7QloZ4)
179
+
170
180
  ## Read more docs
171
181
 
172
182
  - [release notes](versionnotes/)
173
183
  - [gas fakes intro video](https://youtu.be/oEjpIrkYpEM)
174
184
  - [getting started](GETTING_STARTED.md) - how to handle authentication for Workspace scopes.
175
185
  - [readme](README.md)
186
+ - [apps script parity](notes/parity.md)
187
+ - [omlx setup](notes/omlx-setup.md)
176
188
  - [Natural Language Automation with Gemini Skills & MCP Server](notes/gemini-skills-mcp.md) - new skills-based agent approach.
177
189
  - [Add agent skills to gf_agent](https://ramblings.mcpher.com/add-skills-gf_agent/)
178
190
  - [gf_agent documentation](../gf_agent/README.md) - instructions for the Gemini CLI automation agent and MCP server.
179
191
  - [gas fakes cli](notes/gas-fakes-cli.md)
180
- -[local add-on and webapp development with gas-fakes](notes/local-web-development.md)
192
+ - [local add-on and webapp development with gas-fakes](notes/local-web-development.md)
193
+ - [Bringing the webapp home](https://ramblings.mcpher.com/local-apps-script-webapp-and-ui-emulation/)
194
+ - [Local development example code](https://github.com/brucemcpherson/gf-serve)
181
195
  - [github actions using adc](https://github.com/brucemcpherson/gas-fakes-actions-adc)
182
196
  - [github actions using dwd and wif](https://github.com/brucemcpherson/gas-fakes-actions-dwd)
183
197
  - [ksuite as a back end](notes/ksuite_poc.md)
@@ -195,7 +209,7 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
195
209
  - [jdbc notes](notes/jdbc-notes.md)
196
210
  - [Yes – you can run native apps script code on Azure ACA as well!](https://ramblings.mcpher.com/yes-you-can-run-native-apps-script-code-on-azure-aca-as-well/)
197
211
  - [Yes – you can run native apps script code on AWS Lambda!](https://ramblings.mcpher.com/apps-script-on-aws-lambda/)
198
- - [initial idea and thoughts](https://ramblings.mcpher.com/a-proof-of-concept-implementation-of-apps-script-environment-on-node/)
212
+ - [initial idea and thoughts - how it all started](https://ramblings.mcpher.com/a-proof-of-concept-implementation-of-apps-script-environment-on-node/)
199
213
  - [Inside the volatile world of a Google Document](https://ramblings.mcpher.com/inside-the-volatile-world-of-a-google-document/)
200
214
  - [Apps Script Services on Node – using apps script libraries](https://ramblings.mcpher.com/apps-script-services-on-node-using-apps-script-libraries/)
201
215
  - [Apps Script environment on Node – more services](https://ramblings.mcpher.com/apps-script-environment-on-node-more-services/)
package/package.json CHANGED
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "name": "@mcpher/gas-fakes",
41
41
  "author": "bruce mcpherson",
42
- "version": "2.5.2",
42
+ "version": "2.5.4",
43
43
  "license": "MIT",
44
44
  "main": "main.js",
45
45
  "description": "An implementation of the Google Workspace Apps Script runtime: Run native App Script Code on Node and Cloud Run",
package/pngs/srv.jpg ADDED
Binary file
package/src/cli/app.js CHANGED
@@ -219,6 +219,7 @@ export async function main() {
219
219
  .option("-s, --source <string>", "Source directory (default: ./ )")
220
220
  .option("-e, --env <path>", "Path to a custom .env file.", "./.env")
221
221
  .option("--scriptId <string>", "Script ID for the target clasp project.")
222
+ .option("-q, --quiet", "Automatically accept all prompts.")
222
223
  .action(togas);
223
224
 
224
225
  program.showHelpAfterError("(add --help for additional information)");
package/src/cli/togas.js CHANGED
@@ -20,6 +20,7 @@ export async function togas(options) {
20
20
  const scriptId = options.scriptId || process.env.TOGAS_SCRIPT_ID || process.env.GF_SCRIPT_ID;
21
21
  const pattern = options.pattern || process.env.TOGAS_PATTERN || "*";
22
22
  const source = options.source || "./";
23
+ const autoAccept = !!options.quiet;
23
24
 
24
25
  if (!target) {
25
26
  console.error("Error: TOGAS_TARGET is not set. Please run 'gas-fakes init' or provide --target.");
@@ -41,14 +42,18 @@ export async function togas(options) {
41
42
 
42
43
  const claspJsonPath = path.join(absoluteTarget, ".clasp.json");
43
44
  if (!fs.existsSync(claspJsonPath)) {
44
- const response = await prompts({
45
- type: "confirm",
46
- name: "create",
47
- message: `No .clasp.json found in ${target}. Create one?`,
48
- initial: true
49
- });
45
+ let create = autoAccept;
46
+ if (!create) {
47
+ const response = await prompts({
48
+ type: "confirm",
49
+ name: "create",
50
+ message: `No .clasp.json found in ${target}. Create one?`,
51
+ initial: true
52
+ });
53
+ create = response.create;
54
+ }
50
55
 
51
- if (response.create) {
56
+ if (create) {
52
57
  if (!scriptId) {
53
58
  console.error("Error: No Script ID found. Please provide one with --scriptId or in .env.");
54
59
  process.exit(1);
@@ -157,14 +162,18 @@ export async function togas(options) {
157
162
  console.log(`Ready for clasp push in ${target}.`);
158
163
 
159
164
  // 5. Clasp push
160
- const response = await prompts({
161
- type: "confirm",
162
- name: "push",
163
- message: "Push to Apps Script using clasp now?",
164
- initial: true
165
- });
165
+ let push = autoAccept;
166
+ if (!push) {
167
+ const response = await prompts({
168
+ type: "confirm",
169
+ name: "push",
170
+ message: "Push to Apps Script using clasp now?",
171
+ initial: true
172
+ });
173
+ push = response.push;
174
+ }
166
175
 
167
- if (response.push) {
176
+ if (push) {
168
177
  console.log("Running clasp push...");
169
178
  try {
170
179
  execSync("clasp push", { cwd: absoluteTarget, stdio: "inherit" });
package/src/index.js CHANGED
@@ -11,6 +11,7 @@ import './services/gmailapp/app.js'
11
11
  import './services/calendarapp/app.js'
12
12
  import './services/chartsapp/app.js'
13
13
  import './services/session/app.js'
14
+ import './services/base/app.js'
14
15
  import './services/advdrive/app.js'
15
16
  import './services/advsheets/app.js'
16
17
  import './services/advdocs/app.js'
@@ -16,15 +16,15 @@ class FakeAdvSlidesPresentations extends FakeAdvResource {
16
16
  // Override 'get' to use the caching-enabled function fxSlidesGet.
17
17
  get(presentationId, options) {
18
18
  ScriptApp.__behavior.isAccessible(presentationId, 'Slides', 'read');
19
- const { response, data } = this._call('get', { presentationId, ...options }, Syncit.fxSlidesGet);
19
+ const { response, data } = this._call('get', { presentationId, ...options }, {}, null, Syncit.fxSlidesGet);
20
20
  gError(response, 'slides.presentations', 'get');
21
21
  return data;
22
22
  }
23
23
 
24
24
  // Signature matches Apps Script advanced service.
25
25
  create(presentation) {
26
- // The underlying API wants the resource in a 'resource' property.
27
- const result = this._call('create', { resource: presentation });
26
+ // The underlying API wants the resource in a 'requestBody' property for Node.js.
27
+ const result = this._call('create', { requestBody: presentation });
28
28
  if (result.data) {
29
29
  this.slides.__addAllowed(result.data.presentationId);
30
30
  }
@@ -32,12 +32,18 @@ class FakeAdvSlidesPresentations extends FakeAdvResource {
32
32
  }
33
33
 
34
34
  // Signature matches Apps Script advanced service.
35
- batchUpdate(requests, presentationId) {
35
+ // Robustly handle both resource object and raw requests array.
36
+ batchUpdate(resource, presentationId) {
36
37
  // for slides, any batch update is a write
37
38
  ScriptApp.__behavior.isAccessible(presentationId, 'Slides', 'write');
39
+
40
+ const requestBody = Array.isArray(resource)
41
+ ? { requests: resource }
42
+ : resource;
43
+
38
44
  const result = this._call('batchUpdate', {
39
45
  presentationId,
40
- resource: { requests },
46
+ requestBody,
41
47
  });
42
48
  gError(result.response, 'slides.presentations', 'batchUpdate');
43
49
 
@@ -0,0 +1,9 @@
1
+ /**
2
+ * the idea here is to create an empty global entry for the singleton
3
+ * but only load it when it is actually used.
4
+ */
5
+ import { lazyLoaderApp } from '../common/lazyloader.js'
6
+ import { newFakeBase as maker } from './fakebase.js';
7
+
8
+ let _app = null;
9
+ _app = lazyLoaderApp(_app, 'Base', maker);
@@ -0,0 +1,28 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import * as BaseEnums from '../enums/baseenums.js';
3
+ import { newFakeMimeType } from '../mimetype/fakemimetype.js';
4
+
5
+ /**
6
+ * @class FakeBase
7
+ * @description The Base service in Google Apps Script.
8
+ * @see https://developers.google.com/apps-script/reference/base
9
+ */
10
+ export class FakeBase {
11
+ constructor() {
12
+ this.Button = BaseEnums.Button;
13
+ this.ButtonSet = BaseEnums.ButtonSet;
14
+ this.ColorType = BaseEnums.ColorType;
15
+ this.MimeType = newFakeMimeType();
16
+ this.Month = BaseEnums.Month;
17
+ this.Weekday = BaseEnums.Weekday;
18
+ }
19
+
20
+ toString() {
21
+ return 'Base';
22
+ }
23
+ }
24
+
25
+ /**
26
+ * @returns {FakeBase}
27
+ */
28
+ export const newFakeBase = () => Proxies.guard(new FakeBase());
@@ -14,7 +14,7 @@ export class FakeAdvResource {
14
14
  this.__syncitMethod = syncitMethod; // e.g., Syncit.fxDocs or Syncit.fxSheets
15
15
  }
16
16
 
17
- _call(method, params, options, subProp = null) {
17
+ _call(method, params, options, subProp = null, syncMethodOverride = null) {
18
18
  const pack = {
19
19
  prop: this.__serviceName,
20
20
  subProp,
@@ -22,7 +22,8 @@ export class FakeAdvResource {
22
22
  params,
23
23
  options
24
24
  };
25
- const result = this.__syncitMethod(pack);
25
+ const syncMethod = syncMethodOverride || this.__syncitMethod;
26
+ const result = syncMethod(pack);
26
27
  if (!result || !result.response) {
27
28
  // Simulate an error response if the worker didn't provide one.
28
29
  return {
@@ -1,9 +1,10 @@
1
1
  import { FakeTextOutput } from "./textoutput.js";
2
- import { ContentEnums } from "../enums/contentenums.js";
2
+ // just avoid conflict with global MimeType
3
+ import { MimeType as mt } from "../enums/contentenums.js";
3
4
 
4
5
  export class FakeContentService {
5
6
  constructor() {
6
- this.MimeType = ContentEnums.MimeType;
7
+ this.MimeType = mt;
7
8
  }
8
9
 
9
10
  createTextOutput(content = "") {
@@ -0,0 +1,47 @@
1
+ import { newFakeGasenum } from "@mcpher/fake-gasenum";
2
+
3
+ export const Button = newFakeGasenum([
4
+ "CANCEL",
5
+ "CLOSE",
6
+ "NO",
7
+ "OK",
8
+ "YES"
9
+ ])
10
+
11
+ export const ButtonSet = newFakeGasenum([
12
+ "OK",
13
+ "OK_CANCEL",
14
+ "YES_NO",
15
+ "YES_NO_CANCEL"
16
+ ])
17
+
18
+ export const ColorType = newFakeGasenum([
19
+ "RGB",
20
+ "THEME",
21
+ "UNSUPPORTED"
22
+ ])
23
+
24
+ export const Month = newFakeGasenum([
25
+ "JANUARY",
26
+ "FEBRUARY",
27
+ "MARCH",
28
+ "APRIL",
29
+ "MAY",
30
+ "JUNE",
31
+ "JULY",
32
+ "AUGUST",
33
+ "SEPTEMBER",
34
+ "OCTOBER",
35
+ "NOVEMBER",
36
+ "DECEMBER"
37
+ ])
38
+
39
+ export const Weekday = newFakeGasenum([
40
+ "SUNDAY",
41
+ "MONDAY",
42
+ "TUESDAY",
43
+ "WEDNESDAY",
44
+ "THURSDAY",
45
+ "FRIDAY",
46
+ "SATURDAY"
47
+ ])
@@ -1,7 +1,6 @@
1
1
  import { newFakeGasenum } from "@mcpher/fake-gasenum";
2
2
 
3
- export const ContentEnums = {
4
- MimeType: newFakeGasenum([
3
+ export const MimeType = newFakeGasenum([
5
4
  "ATOM",
6
5
  "CSV",
7
6
  "ICAL",
@@ -12,4 +11,3 @@ export const ContentEnums = {
12
11
  "VCARD",
13
12
  "XML"
14
13
  ])
15
- }
@@ -1,6 +1,33 @@
1
- export const ScriptEnums = {
2
- AuthorizationStatus: {
1
+ import { newFakeGasenum } from "@mcpher/fake-gasenum";
2
+
3
+ export const AuthorizationStatus = newFakeGasenum({
3
4
  REQUIRED: 'REQUIRED',
4
5
  NOT_REQUIRED: 'NOT_REQUIRED'
5
- }
6
- };
6
+ })
7
+ export const TriggerSource = newFakeGasenum( {
8
+ CALENDAR: 'CALENDAR',
9
+ CLOCK: 'CLOCK',
10
+ DOCUMENTS: 'DOCUMENTS',
11
+ FORMS: 'FORMS',
12
+ SPREADSHEETS: 'SPREADSHEETS'
13
+ })
14
+ export const AuthMode = newFakeGasenum( {
15
+ NONE: 'NONE',
16
+ CUSTOM_FUNCTION: 'CUSTOM_FUNCTION',
17
+ LIMITED: 'LIMITED',
18
+ FULL: 'FULL'
19
+ })
20
+ export const EventType = newFakeGasenum( {
21
+ CLOCK: 'CLOCK',
22
+ ON_OPEN: 'ON_OPEN',
23
+ ON_EDIT: 'ON_EDIT',
24
+ ON_FORM_SUBMIT: 'ON_FORM_SUBMIT',
25
+ ON_CHANGE: 'ON_CHANGE',
26
+ ON_EVENT_UPDATED: 'ON_EVENT_UPDATED'
27
+ })
28
+ export const InstallationSource = newFakeGasenum({
29
+ APPS_MARKETPLACE_DOMAIN_ADD_ON: 'APPS_MARKETPLACE_DOMAIN_ADD_ON',
30
+ NONE: 'NONE',
31
+ WEB_STORE_ADD_ON: 'WEB_STORE_ADD_ON'
32
+ })
33
+
@@ -142,7 +142,9 @@ export const PageType = newFakeGasenum([
142
142
  "UNSUPPORTED",
143
143
  "SLIDE",
144
144
  "LAYOUT",
145
- "MASTER"
145
+ "MASTER",
146
+ "NOTES_MASTER",
147
+ "NOTES_PAGE"
146
148
  ])
147
149
  export const ParagraphAlignment = newFakeGasenum([
148
150
  "UNSUPPORTED",
@@ -0,0 +1,14 @@
1
+ import { newFakeGasenum } from "@mcpher/fake-gasenum";
2
+
3
+ /**
4
+ * Enum representing the possible content types found in XML.
5
+ */
6
+ export const ContentTypes = newFakeGasenum([
7
+ "CDATA",
8
+ "COMMENT",
9
+ "DOCTYPE",
10
+ "ELEMENT",
11
+ "ENTITYREF",
12
+ "PROCESSINGINSTRUCTION",
13
+ "TEXT"
14
+ ]);
@@ -60,7 +60,7 @@ export class ServerWorkerContext {
60
60
  mainScriptPath: globalThis.__gasFakesMainScriptPath || this._mainScriptPath,
61
61
  controlBuf: this._controlBuf,
62
62
  dataBuf: this._dataBuf,
63
- env: process.env // Pass current environment
63
+ env: { ...process.env, GAS_FAKES_WORKER: 'true' } // Pass current environment and mark as worker
64
64
  },
65
65
  stdout: true,
66
66
  stderr: true
@@ -7,7 +7,13 @@ import { Auth } from '../../support/auth.js'
7
7
  import { Proxies } from '../../support/proxies.js'
8
8
  import { newFakeBehavior } from './behavior.js'
9
9
  import { newFakeAuthorizationInfo } from './fakeauthorizationinfo.js'
10
- import { ScriptEnums } from '../enums/scriptenums.js'
10
+ import {
11
+ AuthMode,
12
+ AuthorizationStatus,
13
+ TriggerSource,
14
+ EventType,
15
+ InstallationSource
16
+ } from '../enums/scriptenums.js'
11
17
  import { newCacheDropin } from '@mcpher/gas-flex-cache'
12
18
  import { slogger } from "../../support/slogger.js";
13
19
  import fs from 'fs';
@@ -48,8 +54,8 @@ const getSourceOAuthToken = () => {
48
54
 
49
55
 
50
56
  const limitMode = (mode) => {
51
- if (mode !== ScriptApp.AuthMode.FULL) {
52
- throw new Error(`only ${ScriptApp.AuthMode.FULL} is supported as mode for now`)
57
+ if (mode !== ScriptApp.AuthMode.FULL.toString()) {
58
+ throw new Error(`only ${ScriptApp.AuthMode.FULL.toString()} is supported as mode for now`)
53
59
  }
54
60
 
55
61
  return mode
@@ -234,10 +240,11 @@ if (typeof globalThis[name] === typeof undefined) {
234
240
  ensureInit()
235
241
  return Auth.getActiveUser()?.id
236
242
  },
237
- AuthMode: {
238
- FULL: 'FULL'
239
- },
240
- AuthorizationStatus: ScriptEnums.AuthorizationStatus,
243
+ AuthMode,
244
+ AuthorizationStatus,
245
+ TriggerSource,
246
+ EventType,
247
+ InstallationSource,
241
248
  getAuthorizationInfo: (authMode) => {
242
249
  ensureInit();
243
250
  return newFakeAuthorizationInfo(authMode);
@@ -1,4 +1,4 @@
1
- import { ScriptEnums } from '../enums/scriptenums.js';
1
+ import { AuthorizationStatus } from '../enums/scriptenums.js';
2
2
 
3
3
  export class FakeAuthorizationInfo {
4
4
  constructor(authMode) {
@@ -8,12 +8,12 @@ export class FakeAuthorizationInfo {
8
8
  getAuthorizationStatus() {
9
9
  // gas-fakes always handles auth out-of-band via CLI,
10
10
  // so during script execution, we assume auth is not required.
11
- return ScriptEnums.AuthorizationStatus.NOT_REQUIRED;
11
+ return AuthorizationStatus.NOT_REQUIRED;
12
12
  }
13
13
 
14
14
  getAuthorizationUrl() {
15
- // Return null since we don't require inline authorization
16
- return null;
15
+ // Return empty string since we don't require inline authorization, matching live Apps Script parity
16
+ return "";
17
17
  }
18
18
  }
19
19
 
@@ -5,5 +5,10 @@
5
5
 
6
6
  import { lazyLoaderApp } from '../common/lazyloader.js'
7
7
  import { newFakeSlidesApp as maker} from './fakeslidesapp.js';
8
+ import './pageelementfactory.js';
9
+ export { newFakePageElementRange } from './fakepageelementrange.js';
10
+ export { newFakePageRange } from './fakepagerange.js';
11
+ export { newFakeTableCellRange } from './faketablecellrange.js';
12
+ export { newFakeTableColumn } from './faketablecolumn.js';
8
13
  let _app = null;
9
14
  _app = lazyLoaderApp(_app, 'SlidesApp', maker)
@@ -39,7 +39,7 @@ export class FakeAutofit {
39
39
  }
40
40
  }];
41
41
 
42
- Slides.Presentations.batchUpdate(requests, presentationId);
42
+ Slides.Presentations.batchUpdate({ requests }, presentationId);
43
43
  return this;
44
44
  }
45
45
 
@@ -0,0 +1,106 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import { newFakeLineFill } from './fakelinefill.js';
3
+
4
+ /**
5
+ * Represents the Border properties of a Slides element.
6
+ * Emulates the Slides API resource structure.
7
+ */
8
+ export const newFakeBorder = (...args) => {
9
+ return Proxies.guard(new FakeBorder(...args));
10
+ };
11
+
12
+ export class FakeBorder {
13
+ /**
14
+ * @param {FakePageElement} parent - The element this border belongs to.
15
+ */
16
+ constructor(parent) {
17
+ this._parent = parent;
18
+ }
19
+
20
+ get __resource() {
21
+ // Border properties are typically in imageProperties.outline or shapeProperties.outline
22
+ const res = this._parent.__resource;
23
+ return res.image?.imageProperties?.outline || res.shape?.shapeProperties?.outline;
24
+ }
25
+
26
+ getLineFill() {
27
+ return newFakeLineFill(this._parent);
28
+ }
29
+
30
+ isVisible() {
31
+ const weight = this.getWeight();
32
+ return weight !== null && weight > 0;
33
+ }
34
+
35
+ setDashStyle(style) {
36
+ this.__update({ dashStyle: style.toString() }, 'dashStyle');
37
+ return this;
38
+ }
39
+
40
+ setTransparent() {
41
+ this.__update({ propertyState: 'NOT_RENDERED' }, 'propertyState');
42
+ return this;
43
+ }
44
+
45
+ setWeight(weight) {
46
+ this.__update({ weight: { magnitude: weight, unit: 'PT' }, propertyState: 'RENDERED' }, 'weight,propertyState');
47
+ return this;
48
+ }
49
+
50
+ __update(props, fields) {
51
+ const presentationId = this._parent.__presentation?.getId() || this._parent.__page?.__presentation?.getId();
52
+ const type = this._parent.getPageElementType().toString();
53
+ const objectId = this._parent.getObjectId();
54
+
55
+ let request = null;
56
+ if (type === 'SHAPE') {
57
+ request = {
58
+ updateShapeProperties: {
59
+ objectId,
60
+ shapeProperties: { outline: props },
61
+ fields: fields ? fields.split(',').map(f => `outline.${f}`).join(',') : 'outline'
62
+ }
63
+ };
64
+ } else if (type === 'IMAGE') {
65
+ request = {
66
+ updateImageProperties: {
67
+ objectId,
68
+ imageProperties: { outline: props },
69
+ fields: fields ? fields.split(',').map(f => `outline.${f}`).join(',') : 'outline'
70
+ }
71
+ };
72
+ }
73
+
74
+ if (request) {
75
+ Slides.Presentations.batchUpdate({ requests: [request] }, presentationId);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Retrieves the weight of the border in Points (PT).
81
+ * @returns {number | null} The border weight in PT, or null if not defined/rendered.
82
+ */
83
+ getWeight() {
84
+ const outline = this.__resource;
85
+ if (!outline || outline.propertyState === 'NOT_RENDERED' || outline.weight === undefined) {
86
+ return null;
87
+ }
88
+ return this._parent.__normalize(outline.weight);
89
+ }
90
+
91
+ /**
92
+ * Gets the DashStyle of the border.
93
+ * @returns {string} The dash style.
94
+ */
95
+ getDashStyle() {
96
+ const outline = this.__resource;
97
+ if (!outline || outline.propertyState === 'NOT_RENDERED' || !outline.dashStyle) {
98
+ return 'SOLID';
99
+ }
100
+ return outline.dashStyle;
101
+ }
102
+
103
+ toString() {
104
+ return 'Border';
105
+ }
106
+ }
@@ -150,7 +150,7 @@ export class FakeColorScheme {
150
150
  }
151
151
  }];
152
152
 
153
- Slides.Presentations.batchUpdate(requests, presentationId);
153
+ Slides.Presentations.batchUpdate({ requests }, presentationId);
154
154
  return this;
155
155
  }
156
156