@mcpher/gas-fakes 2.3.17 → 2.5.1

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 (89) hide show
  1. package/README.md +15 -32
  2. package/package.json +1 -2
  3. package/src/cli/app.js +30 -2
  4. package/src/cli/server.js +32 -0
  5. package/src/cli/setup.js +24 -0
  6. package/src/cli/togas.js +176 -0
  7. package/src/index.js +2 -0
  8. package/src/services/common/fakeui.js +45 -0
  9. package/src/services/content/app.js +3 -0
  10. package/src/services/content/contentservice.js +14 -0
  11. package/src/services/content/textoutput.js +45 -0
  12. package/src/services/documentapp/fakedocumentapp.js +1 -1
  13. package/src/services/enums/contentenums.js +15 -0
  14. package/src/services/enums/htmlenums.js +13 -0
  15. package/src/services/enums/scriptenums.js +6 -0
  16. package/src/services/formapp/fakeformapp.js +5 -0
  17. package/src/services/html/app.js +9 -0
  18. package/src/services/html/consumerworker.js +129 -0
  19. package/src/services/html/googlescriptrun.js +91 -0
  20. package/src/services/html/htmloutput.js +127 -0
  21. package/src/services/html/htmloutputmetatag.js +14 -0
  22. package/src/services/html/htmlservice.js +94 -0
  23. package/src/services/html/htmltemplate.js +63 -0
  24. package/src/services/html/serverworker.js +135 -0
  25. package/src/services/html/webapp.js +266 -0
  26. package/src/services/html/worker.js +63 -0
  27. package/src/services/libhandlerapp/fakelibrary.js +2 -2
  28. package/src/services/scriptapp/app.js +44 -0
  29. package/src/services/scriptapp/fakeauthorizationinfo.js +22 -0
  30. package/src/services/slidesapp/fakeslidesapp.js +5 -0
  31. package/src/services/spreadsheetapp/fakebooleancondition.js +14 -2
  32. package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +30 -5
  33. package/src/services/spreadsheetapp/fakegradientcondition.js +1 -1
  34. package/src/services/spreadsheetapp/fakeovergridimage.js +25 -0
  35. package/src/services/spreadsheetapp/fakesheet.js +23 -1
  36. package/src/services/spreadsheetapp/fakespreadsheet.js +68 -11
  37. package/src/services/spreadsheetapp/fakespreadsheetapp.js +70 -9
  38. package/src/services/stores/fakestores.js +5 -0
  39. package/src/support/auth.js +2 -0
  40. package/src/support/proxies.js +1 -1
  41. package/src/support/sxauth.js +20 -12
  42. package/src/support/utils.js +480 -200
  43. package/src/support/workersync/sxhtml.js +8 -0
  44. package/src/support/workersync/synchronizer.js +8 -1
  45. package/src/support/workersync/worker.js +5 -0
  46. package/api-docs/kdrive_api.json +0 -69958
  47. package/appsscript.json +0 -102
  48. package/gf_agent/README.md +0 -101
  49. package/gf_agent/SKILL.md +0 -460
  50. package/gf_agent/documentation.md +0 -105
  51. package/gf_agent/gf-agent-contributor/SKILL.md +0 -56
  52. package/gf_agent/index.md +0 -21
  53. package/gf_agent/knowledge/00-execution-context.md +0 -5
  54. package/gf_agent/knowledge/01-drive.md +0 -12
  55. package/gf_agent/knowledge/02-syntax.md +0 -14
  56. package/gf_agent/knowledge/03-auth.md +0 -15
  57. package/gf_agent/knowledge/04-advanced.md +0 -46
  58. package/gf_agent/knowledge/05-sheets-forms.md +0 -26
  59. package/gf_agent/knowledge/06-jdbc-cloudsql.md +0 -21
  60. package/gf_agent/knowledge/07-jdbc-auth-details.md +0 -30
  61. package/gf_agent/knowledge/08-docs-limitations.md +0 -4
  62. package/gf_agent/knowledge/09-orchestrator-pattern.md +0 -55
  63. package/gf_agent/knowledge/10-sandbox-security.md +0 -62
  64. package/gf_agent/knowledge/11-chart-builder-limitations.md +0 -15
  65. package/gf_agent/knowledge/12-gmail-eventual-consistency.md +0 -13
  66. package/gf_agent/knowledge/13-advanced-services-discovery.md +0 -23
  67. package/gf_agent/knowledge/14-utilities-parity.md +0 -13
  68. package/gf_agent/knowledge/README.md +0 -16
  69. package/gf_agent/scripts/SKILL.template.md +0 -63
  70. package/gf_agent/scripts/builder.js +0 -118
  71. package/gf_agent/skills/base.md +0 -156
  72. package/gf_agent/skills/cache.md +0 -20
  73. package/gf_agent/skills/calendar.md +0 -780
  74. package/gf_agent/skills/charts.md +0 -127
  75. package/gf_agent/skills/document.md +0 -6752
  76. package/gf_agent/skills/drive.md +0 -423
  77. package/gf_agent/skills/forms.md +0 -4036
  78. package/gf_agent/skills/gmail.md +0 -576
  79. package/gf_agent/skills/jdbc.md +0 -3101
  80. package/gf_agent/skills/lock.md +0 -20
  81. package/gf_agent/skills/properties.md +0 -19
  82. package/gf_agent/skills/script.md +0 -50
  83. package/gf_agent/skills/slides.md +0 -5054
  84. package/gf_agent/skills/spreadsheet.md +0 -56075
  85. package/gf_agent/skills/urlfetch.md +0 -28
  86. package/gf_agent/skills/utilities.md +0 -50
  87. package/gf_agent/skills/xml.md +0 -270
  88. package/skills-lock.json +0 -10
  89. package/src/services/documentapp/fakeui.js +0 -27
@@ -35,13 +35,14 @@ export class FakeSpreadsheet {
35
35
  constructor(file) {
36
36
  // when we insert/delete sheets row/cols we update this metadata too
37
37
  this.__meta = file;
38
+ this.__activeRange = null;
39
+ this.__activeSheet = null;
38
40
 
39
41
  // may of these props can be picked up from the Drive API, so we'll look as a file too
40
42
  this.__file = DriveApp.getFileById(file.spreadsheetId);
41
43
 
42
44
  const props = [
43
45
  "getSpreadsheetTheme",
44
- "setActiveSheet",
45
46
 
46
47
  "getBandings",
47
48
  "getDataSources",
@@ -102,12 +103,7 @@ export class FakeSpreadsheet {
102
103
  "isReadable",
103
104
  "isWritable",
104
105
  "getSelection",
105
- "setActiveRangeList",
106
- "setActiveRange",
107
106
  "getActiveRangeList",
108
- "getCurrentCell",
109
- "setCurrentCell",
110
- "getActiveRange",
111
107
  "deleteRow",
112
108
  "hideRow",
113
109
  "appendRow",
@@ -133,9 +129,7 @@ export class FakeSpreadsheet {
133
129
  "getFrozenColumns",
134
130
  "unhideColumn",
135
131
  "insertColumnsBefore",
136
- "setActiveSelection",
137
132
  "getDataSourceFormulas",
138
- "insertImage",
139
133
  "getSheetPermissions",
140
134
  "insertColumnBefore",
141
135
 
@@ -160,16 +154,79 @@ export class FakeSpreadsheet {
160
154
  return notYetImplemented(f);
161
155
  };
162
156
  });
157
+ }
163
158
 
159
+ setActiveRange(range) {
160
+ this.__activeRange = range;
161
+ this.__activeSheet = range.getSheet();
162
+ this.__currentCell = range.getCell(1, 1);
163
+
164
+ // Sync with SpreadsheetApp if this is the active spreadsheet
165
+ const app = SpreadsheetApp;
166
+ if (app && app.getActiveSpreadsheet() === this) {
167
+ app.setActiveSpreadsheet(this);
168
+ }
169
+ return range;
170
+ }
171
+
172
+ setActiveRangeList(rangeList) {
173
+ const ranges = rangeList.getRanges();
174
+ if (ranges && ranges.length > 0) {
175
+ this.setActiveRange(ranges[ranges.length - 1]);
176
+ }
177
+ return rangeList;
178
+ }
179
+
180
+ setActiveSelection(rangeOrA1Notation) {
181
+ const { nargs, matchThrow } = signatureArgs(arguments, "Spreadsheet.setActiveSelection");
182
+ if (nargs !== 1) matchThrow();
183
+
184
+ let range;
185
+ if (is.string(rangeOrA1Notation)) {
186
+ range = this.getRange(rangeOrA1Notation);
187
+ } else {
188
+ range = rangeOrA1Notation;
189
+ }
190
+ return this.setActiveRange(range);
191
+ }
192
+
193
+ getActiveRange() {
194
+ return this.__activeRange || this.getActiveSheet().getRange('A1');
195
+ }
196
+
197
+ getCurrentCell() {
198
+ return this.__currentCell || this.getActiveSheet().getRange('A1');
199
+ }
200
+
201
+ setCurrentCell(cell) {
202
+ this.__currentCell = cell;
203
+ this.__activeSheet = cell.getSheet();
204
+ // Setting current cell does not change the active range, just the cell within it.
205
+ // However, if there is no active range, it becomes the active range.
206
+ if (!this.__activeRange) {
207
+ this.__activeRange = cell;
208
+ }
209
+ return cell;
210
+ }
211
+
212
+ setActiveSheet(sheet, restoreSelection) {
213
+ const { nargs, matchThrow } = signatureArgs(arguments, "Spreadsheet.setActiveSheet");
214
+ if (nargs < 1 || nargs > 2) matchThrow();
215
+ this.__activeSheet = sheet;
216
+ return sheet;
164
217
  }
165
218
 
166
- // note this is a workaround as we don't have the concept of active sheet in a non bound document
167
- // so instead we'll just get the first sheet for now
168
- // TODO - something better
169
219
  getActiveSheet() {
220
+ if (this.__activeSheet) return this.__activeSheet;
170
221
  return this.__getFirstSheet();
171
222
  }
172
223
 
224
+ insertImage(blobSourceOrUrl, column, row, offsetX, offsetY) {
225
+ const { nargs, matchThrow } = signatureArgs(arguments, "Spreadsheet.insertImage");
226
+ if (nargs < 3 || nargs > 5) matchThrow();
227
+ return this.getActiveSheet().insertImage(blobSourceOrUrl, column, row, offsetX, offsetY);
228
+ }
229
+
173
230
  addViewer(emailAddress) {
174
231
  this.__file.addViewer(emailAddress);
175
232
  return this;
@@ -16,6 +16,7 @@ import { newFakeConditionalFormatRuleBuilder } from "./fakeconditionalformatrule
16
16
  import { newFakeDataSourceSpecBuilder } from "./fakedatasourcespecbuilder.js";
17
17
  import { newFakeTextFinder } from "./faketextfinder.js";
18
18
  import { newFakeCellImageBuilder } from "./fakecellimagebuilder.js";
19
+ import { newFakeUi } from "../common/fakeui.js";
19
20
 
20
21
  import * as Enums from "../enums/sheetsenums.js";
21
22
 
@@ -81,16 +82,8 @@ export class FakeSpreadsheetApp {
81
82
 
82
83
  const props = [
83
84
  "getActive",
84
- "getActiveSheet",
85
- "getCurrentCell",
86
- "getActiveRange",
87
85
  "getActiveRangeList",
88
86
  "getSelection",
89
- "setActiveSheet",
90
- "setCurrentCell",
91
- "setActiveRange",
92
- "setActiveRangeList",
93
- "getUi",
94
87
 
95
88
  "ChartAggregationType",
96
89
  "ChartTransformationType",
@@ -102,6 +95,11 @@ export class FakeSpreadsheetApp {
102
95
  };
103
96
  });
104
97
  }
98
+
99
+ getUi() {
100
+ return newFakeUi();
101
+ }
102
+
105
103
  toString() {
106
104
  return "SpreadsheetApp";
107
105
  }
@@ -124,8 +122,11 @@ export class FakeSpreadsheetApp {
124
122
  if (this.__activeSpreadsheet) return this.__activeSpreadsheet;
125
123
  // because this is a faked container bound app, we need to get the documentId from the config file
126
124
  const documentId = Auth.getDocumentId();
125
+ console.log('Worker Auth.getDocumentId():', documentId);
127
126
  if (documentId) {
128
- return this.openById(documentId);
127
+ const ss = this.openById(documentId);
128
+ //console.log('Worker openById result:', ss);
129
+ return ss;
129
130
  }
130
131
  return null;
131
132
  }
@@ -135,6 +136,24 @@ export class FakeSpreadsheetApp {
135
136
  return this;
136
137
  }
137
138
 
139
+ getActiveSheet() {
140
+ const ss = this.getActiveSpreadsheet();
141
+ if (ss) return ss.getActiveSheet();
142
+ return null;
143
+ }
144
+
145
+ setActiveSheet(sheet, restoreSelection) {
146
+ const { nargs, matchThrow } = signatureArgs(arguments, "SpreadsheetApp.setActiveSheet");
147
+ if (nargs < 1 || nargs > 2) matchThrow();
148
+
149
+ // According to GAS docs, setActiveSheet on SpreadsheetApp also sets the active spreadsheet
150
+ const ss = sheet.getParent();
151
+ if (ss) {
152
+ this.setActiveSpreadsheet(ss);
153
+ ss.setActiveSheet(sheet, restoreSelection);
154
+ }
155
+ return sheet;
156
+ }
138
157
 
139
158
  enableBigQueryExecution() {
140
159
  const { nargs, matchThrow } = signatureArgs(
@@ -145,6 +164,48 @@ export class FakeSpreadsheetApp {
145
164
  // This is a no-op in the fake environment, as there's no real execution to enable.
146
165
  }
147
166
 
167
+ getActiveRange() {
168
+ const ss = this.getActiveSpreadsheet();
169
+ if (ss) return ss.getActiveRange();
170
+ return null;
171
+ }
172
+
173
+ setActiveRange(range) {
174
+ const ss = range.getSheet().getParent();
175
+ if (ss) {
176
+ this.setActiveSpreadsheet(ss);
177
+ return ss.setActiveRange(range);
178
+ }
179
+ return range;
180
+ }
181
+
182
+ setActiveRangeList(rangeList) {
183
+ const ranges = rangeList.getRanges();
184
+ if (ranges && ranges.length > 0) {
185
+ const ss = ranges[0].getSheet().getParent();
186
+ if (ss) {
187
+ this.setActiveSpreadsheet(ss);
188
+ return ss.setActiveRangeList(rangeList);
189
+ }
190
+ }
191
+ return rangeList;
192
+ }
193
+
194
+ getCurrentCell() {
195
+ const ss = this.getActiveSpreadsheet();
196
+ if (ss) return ss.getCurrentCell();
197
+ return null;
198
+ }
199
+
200
+ setCurrentCell(cell) {
201
+ const ss = cell.getSheet().getParent();
202
+ if (ss) {
203
+ this.setActiveSpreadsheet(ss);
204
+ return ss.setCurrentCell(cell);
205
+ }
206
+ return cell;
207
+ }
208
+
148
209
  enableAllDataSourcesExecution() {
149
210
  const { nargs, matchThrow } = signatureArgs(
150
211
  arguments,
@@ -191,6 +191,11 @@ export const newFakeService = (kind) => {
191
191
  const selectCache = (domain, kind, defaultExpirationSeconds) => {
192
192
  // actually we might be overriding the type of service
193
193
  domain = validateProp(domain, StoreDomain, 'store_domain')
194
+
195
+ if (domain === StoreDomain.USER && !Auth.getUserId()) {
196
+ throw new Error('User Properties/Cache requested, but no authenticated user identity was found. Ensure you have authenticated via \'gas-fakes auth\'.');
197
+ }
198
+
194
199
  const which = whichCache()
195
200
  if (which.type === "UPSTASH") {
196
201
  const models = getStoreModels()
@@ -6,6 +6,7 @@ import { clearFileCache } from "./filecache.js";
6
6
 
7
7
  // Multi-identity storage
8
8
  export const _identities = new Map();
9
+
9
10
  // Prefer 'google' as the default platform if it is authorized in the environment
10
11
  let _platform = process.env.GF_PLATFORM_AUTH ? (process.env.GF_PLATFORM_AUTH.includes('google') ? 'google' : process.env.GF_PLATFORM_AUTH.split(',')[0]) : 'google';
11
12
  let _manifest = null;
@@ -361,6 +362,7 @@ export const Auth = {
361
362
  getAuthedScopes,
362
363
  getScriptId,
363
364
  getDocumentId,
365
+ getSettings,
364
366
  setSettings,
365
367
  getCachePath,
366
368
  getPropertiesPath,
@@ -78,7 +78,7 @@ const registerProxy = (name, getApp) => {
78
78
  Object.defineProperty(globalThis, name, {
79
79
  value,
80
80
  enumerable: true,
81
- configurable: false,
81
+ configurable: true,
82
82
  writable: false,
83
83
  });
84
84
  }
@@ -52,6 +52,8 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, cachePath,
52
52
  getIfExists(claspFile)
53
53
  ])
54
54
 
55
+ const manifestExists = Object.keys(manifest).length > 0;
56
+
55
57
  // Emulate manifest scopes from .env if missing or empty
56
58
  if (!manifest.oauthScopes || manifest.oauthScopes.length === 0) {
57
59
  const envScopes = Array.from(new Set([
@@ -65,10 +67,18 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, cachePath,
65
67
  manifest.timeZone = process.env.GF_TIMEZONE || "America/New_York";
66
68
  }
67
69
  if (!_loggedSummary) {
68
- syncLog(`...appsscript.json missing or missing scopes. Emulating manifest using scopes from .env file`);
70
+ if (manifestExists) {
71
+ syncLog(`...appsscript.json found but 'oauthScopes' is missing. Emulating scopes from .env file`);
72
+ } else {
73
+ syncLog(`...appsscript.json missing. Emulating manifest using scopes from .env file`);
74
+ }
69
75
  }
70
76
  } else if (!_loggedSummary) {
71
- syncWarn(`...Warning: No appsscript.json found and .env file missing or no DEFAULT_SCOPES/EXTRA_SCOPES defined in .env. Downstream API calls may fail with 'insufficient authentication scopes'.`);
77
+ if (manifestExists) {
78
+ syncWarn(`...Warning: appsscript.json found but 'oauthScopes' is missing, and no DEFAULT_SCOPES/EXTRA_SCOPES defined in .env. Downstream API calls may fail with 'insufficient authentication scopes'.`);
79
+ } else {
80
+ syncWarn(`...Warning: No appsscript.json found and no DEFAULT_SCOPES/EXTRA_SCOPES defined in .env. Downstream API calls may fail with 'insufficient authentication scopes'.`);
81
+ }
72
82
  }
73
83
  }
74
84
 
@@ -84,6 +94,7 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, cachePath,
84
94
  const identities = {};
85
95
 
86
96
  // --- Google Auth Block ---
97
+ let finalScopes = [];
87
98
  if (platforms.includes('google')) {
88
99
  try {
89
100
  // Ensure platform is set for info discovery
@@ -97,7 +108,7 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, cachePath,
97
108
  ]
98
109
  const scopeSet = new Set(scopes)
99
110
  mandatoryScopes.forEach(scope => scopeSet.add(scope))
100
- const finalScopes = Array.from(scopeSet)
111
+ finalScopes = Array.from(scopeSet)
101
112
 
102
113
  // Check for file-type scopes that also require Drive scope
103
114
  const hasWorkspaceFileScope = finalScopes.some(s =>
@@ -153,27 +164,24 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, cachePath,
153
164
 
154
165
  // Provide guidance for Domain Wide Delegation issues
155
166
  if (err.message.includes('unauthorized_client')) {
156
- const clientId = Auth.getClientId();
157
167
  const msg = [
158
168
  "",
159
169
  "=".repeat(80),
160
170
  "GOOGLE AUTHENTICATION ERROR: unauthorized_client",
161
- "This usually means Domain-Wide Delegation (DWD) is missing for one or more scopes.",
162
- "",
163
- `Your Service Account Client ID is: ${clientId || 'unknown (check your service account JSON file)'}`,
171
+ "The scopes defined in your appsscript.json are not currently authorized.",
164
172
  "",
165
- "The following scopes should be authorized in the Google Admin Console:",
173
+ "The following scopes are required:",
166
174
  finalScopes.join(","),
167
175
  "",
168
176
  "To fix this:",
169
- "1. Go to https://admin.google.com",
170
- "2. Security -> Access and data control -> API controls",
171
- "3. Manage Domain Wide Delegation",
172
- "4. Find/Add your Client ID and ensure the list of scopes above matches exactly.",
177
+ "1. Run 'gas-fakes init'",
178
+ "2. Run 'gas-fakes auth' to re-authenticate and sync your appsscript.json scopes.",
179
+ "3. If this is a Service Account with Domain-Wide Delegation (DWD), ensure these scopes are also added in the Google Admin Console (Security -> API Controls).",
173
180
  "=".repeat(80),
174
181
  ""
175
182
  ].join("\n");
176
183
  console.error(msg);
184
+ process.exit(1);
177
185
  }
178
186
 
179
187
  if (!platforms.includes('ksuite') && !platforms.includes('msgraph')) throw err;