@mcpher/gas-fakes 2.3.10 → 2.3.13
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.
- package/README.md +14 -14
- package/gas-fakes.js +1 -0
- package/gf_agent/scripts/builder.js +26 -1
- package/package.json +1 -1
- package/src/cli/lib-manager.js +14 -4
- package/src/cli/setup.js +17 -4
- package/src/services/chartsapp/fakechartsapp.js +6 -1
- package/src/services/documentapp/elementhelpers.js +27 -0
- package/src/services/documentapp/fakeelement.js +8 -0
- package/src/services/documentapp/fakeparagraph.js +14 -0
- package/src/services/documentapp/faketext.js +174 -4
- package/src/services/driveapp/driveiterators.js +53 -8
- package/src/services/driveapp/fakedriveapp.js +49 -15
- package/src/services/driveapp/fakedrivefile.js +105 -0
- package/src/services/driveapp/fakedrivefolder.js +8 -0
- package/src/services/driveapp/fakedrivemeta.js +68 -16
- package/src/services/driveapp/fakefolderapp.js +19 -6
- package/src/services/enums/chartsenums.js +53 -20
- package/src/services/enums/driveenums.js +1 -0
- package/src/services/slidesapp/fakeautofit.js +23 -15
- package/src/services/slidesapp/fakecolorscheme.js +160 -0
- package/src/services/slidesapp/fakelayout.js +11 -1
- package/src/services/slidesapp/fakemaster.js +10 -0
- package/src/services/slidesapp/fakepresentation.js +27 -0
- package/src/services/slidesapp/fakeslide.js +9 -0
- package/src/services/slidesapp/faketextrange.js +6 -11
- package/src/services/spreadsheetapp/chartenummapping.js +15 -0
- package/src/services/spreadsheetapp/fakebooleancondition.js +119 -0
- package/src/services/spreadsheetapp/fakecellimage.js +42 -0
- package/src/services/spreadsheetapp/fakecellimagebuilder.js +59 -0
- package/src/services/spreadsheetapp/fakeconditionalformatrule.js +55 -0
- package/src/services/spreadsheetapp/fakeconditionalformatrulebuilder.js +330 -0
- package/src/services/spreadsheetapp/fakedevelopermetadata.js +32 -4
- package/src/services/spreadsheetapp/fakedevelopermetadatalocation.js +27 -5
- package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +155 -21
- package/src/services/spreadsheetapp/fakegradientcondition.js +71 -0
- package/src/services/spreadsheetapp/fakesheet.js +63 -0
- package/src/services/spreadsheetapp/fakesheetrange.js +12 -1
- package/src/services/spreadsheetapp/fakespreadsheet.js +30 -11
- package/src/services/spreadsheetapp/fakespreadsheetapp.js +21 -3
- package/src/services/urlfetchapp/app.js +33 -1
- package/src/support/fileiterators.js +3 -1
- package/src/support/filesharers.js +7 -3
- package/src/support/peeker.js +8 -2
- package/src/support/sheetutils.js +1 -0
- package/src/support/sxdrive.js +26 -15
- package/src/support/syncit.js +4 -6
- package/src/support/workersync/synchronizer.js +24 -4
- package/src/support/workersync/worker.js +13 -2
- package/summarize_advanced.js +0 -69
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js';
|
|
2
|
+
import { makeColorFromApi } from '../common/fakecolorbuilder.js';
|
|
3
|
+
import { InterpolationType } from '../enums/sheetsenums.js';
|
|
4
|
+
|
|
5
|
+
export const newFakeGradientCondition = (...args) => {
|
|
6
|
+
return Proxies.guard(new FakeGradientCondition(...args));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class FakeGradientCondition {
|
|
10
|
+
constructor(apiGradient) {
|
|
11
|
+
this.__apiGradient = apiGradient || {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
__getColor(point) {
|
|
15
|
+
if (!point) return null;
|
|
16
|
+
if (point.colorStyle) return makeColorFromApi(point.colorStyle);
|
|
17
|
+
if (point.color) return makeColorFromApi({ rgbColor: point.color });
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
__getType(point) {
|
|
22
|
+
if (!point || !point.type) return null;
|
|
23
|
+
return InterpolationType[point.type] || null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
__getValue(point) {
|
|
27
|
+
if (!point) return "";
|
|
28
|
+
if (point.type === 'MIN' || point.type === 'MAX') return "";
|
|
29
|
+
return point.value !== undefined ? point.value : "";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getMaxColorObject() {
|
|
33
|
+
return this.__getColor(this.__apiGradient.maxpoint);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getMaxType() {
|
|
37
|
+
return this.__getType(this.__apiGradient.maxpoint);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getMaxValue() {
|
|
41
|
+
return this.__getValue(this.__apiGradient.maxpoint);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getMidColorObject() {
|
|
45
|
+
return this.__getColor(this.__apiGradient.midpoint);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getMidType() {
|
|
49
|
+
return this.__getType(this.__apiGradient.midpoint);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getMidValue() {
|
|
53
|
+
return this.__getValue(this.__apiGradient.midpoint);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getMinColorObject() {
|
|
57
|
+
return this.__getColor(this.__apiGradient.minpoint);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getMinType() {
|
|
61
|
+
return this.__getType(this.__apiGradient.minpoint);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getMinValue() {
|
|
65
|
+
return this.__getValue(this.__apiGradient.minpoint);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
toString() {
|
|
69
|
+
return 'GradientCondition';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -5,6 +5,7 @@ import { Utils } from "../../support/utils.js";
|
|
|
5
5
|
import { SheetUtils } from "../../support/sheetutils.js";
|
|
6
6
|
import { batchUpdate, makeSheetsGridRange } from "./sheetrangehelpers.js";
|
|
7
7
|
import { newFakeFilter } from "./fakefilter.js";
|
|
8
|
+
import { newFakeConditionalFormatRule } from "./fakeconditionalformatrule.js";
|
|
8
9
|
import { newFakePivotTable } from "./fakepivottable.js";
|
|
9
10
|
import { newFakeBanding } from "./fakebanding.js";
|
|
10
11
|
import { newFakeDeveloperMetadataFinder } from "./fakedevelopermetadatafinder.js";
|
|
@@ -378,6 +379,68 @@ export class FakeSheet {
|
|
|
378
379
|
: null;
|
|
379
380
|
}
|
|
380
381
|
|
|
382
|
+
/**
|
|
383
|
+
* clearConditionalFormatRules() https://developers.google.com/apps-script/reference/spreadsheet/sheet#clearconditionalformatrules
|
|
384
|
+
* Removes all conditional format rules from the sheet. Equivalent to calling setConditionalFormatRules(rules) with an empty array as input.
|
|
385
|
+
*/
|
|
386
|
+
clearConditionalFormatRules() {
|
|
387
|
+
this.setConditionalFormatRules([]);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* getConditionalFormatRules() https://developers.google.com/apps-script/reference/spreadsheet/sheet#getconditionalformatrules
|
|
392
|
+
* Get all conditional format rules in this sheet.
|
|
393
|
+
* @returns {FakeConditionalFormatRule[]} An array of all rules in the sheet.
|
|
394
|
+
*/
|
|
395
|
+
getConditionalFormatRules() {
|
|
396
|
+
const meta = this.getParent().__getMetaProps(
|
|
397
|
+
`sheets(conditionalFormats,properties.sheetId)`
|
|
398
|
+
);
|
|
399
|
+
const sheetMeta = meta.sheets.find(
|
|
400
|
+
(s) => s.properties.sheetId === this.getSheetId()
|
|
401
|
+
);
|
|
402
|
+
const rules = sheetMeta?.conditionalFormats || [];
|
|
403
|
+
return rules.map(rule => newFakeConditionalFormatRule(rule, this));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* setConditionalFormatRules(rules) https://developers.google.com/apps-script/reference/spreadsheet/sheet#setconditionalformatrulesrules
|
|
408
|
+
* Replaces all currently existing conditional format rules in the sheet with the input rules. Rules are evaluated in their input order.
|
|
409
|
+
* @param {FakeConditionalFormatRule[]} rules The array of conditional format rules to set.
|
|
410
|
+
*/
|
|
411
|
+
setConditionalFormatRules(rules) {
|
|
412
|
+
const { nargs, matchThrow } = signatureArgs(arguments, "Sheet.setConditionalFormatRules");
|
|
413
|
+
if (nargs !== 1 || !Array.isArray(rules)) matchThrow();
|
|
414
|
+
|
|
415
|
+
const requests = [];
|
|
416
|
+
|
|
417
|
+
// Delete existing rules
|
|
418
|
+
const existingRules = this.getConditionalFormatRules();
|
|
419
|
+
const existingCount = existingRules.length;
|
|
420
|
+
for (let i = existingCount - 1; i >= 0; i--) {
|
|
421
|
+
requests.push({
|
|
422
|
+
deleteConditionalFormatRule: {
|
|
423
|
+
sheetId: this.getSheetId(),
|
|
424
|
+
index: i
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Add new rules
|
|
430
|
+
rules.forEach((rule, index) => {
|
|
431
|
+
requests.push({
|
|
432
|
+
addConditionalFormatRule: {
|
|
433
|
+
rule: rule.__apiRule,
|
|
434
|
+
index: index
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
if (requests.length > 0) {
|
|
440
|
+
Sheets.Spreadsheets.batchUpdate({ requests: requests }, this.__parent.getId());
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
381
444
|
getPivotTables() {
|
|
382
445
|
const meta = this.getParent().__getMetaProps(
|
|
383
446
|
`sheets(data(rowData(values(pivotTable))),properties.sheetId)`
|
|
@@ -2611,6 +2611,17 @@ skipFilteredRows Boolean Whether to avoid clearing filtered rows.
|
|
|
2611
2611
|
// options = { valueInputOption: "RAW" },
|
|
2612
2612
|
options = { valueInputOption: "USER_ENTERED" },
|
|
2613
2613
|
}) {
|
|
2614
|
+
// Intercept FakeCellImage objects and convert to =IMAGE(url) formula
|
|
2615
|
+
const processedValues = values.map(row =>
|
|
2616
|
+
row.map(cell => {
|
|
2617
|
+
if (cell && typeof cell === 'object' && cell.toString() === 'CellImage') {
|
|
2618
|
+
const url = cell._properties?.sourceUrl || null;
|
|
2619
|
+
return url ? `=IMAGE("${url}")` : "";
|
|
2620
|
+
}
|
|
2621
|
+
return cell;
|
|
2622
|
+
})
|
|
2623
|
+
);
|
|
2624
|
+
|
|
2614
2625
|
const range = single
|
|
2615
2626
|
? this.__getRangeWithSheet(this.__getTopLeft())
|
|
2616
2627
|
: this.__getWithSheet();
|
|
@@ -2620,7 +2631,7 @@ skipFilteredRows Boolean Whether to avoid clearing filtered rows.
|
|
|
2620
2631
|
{
|
|
2621
2632
|
majorDimension: "ROWS",
|
|
2622
2633
|
range,
|
|
2623
|
-
values,
|
|
2634
|
+
values: processedValues,
|
|
2624
2635
|
},
|
|
2625
2636
|
],
|
|
2626
2637
|
};
|
|
@@ -152,17 +152,6 @@ export class FakeSpreadsheet {
|
|
|
152
152
|
"deleteColumn",
|
|
153
153
|
"getImages",
|
|
154
154
|
"find",
|
|
155
|
-
"sort",
|
|
156
|
-
"addEditor",
|
|
157
|
-
"addEditors",
|
|
158
|
-
"addViewers",
|
|
159
|
-
"addViewer",
|
|
160
|
-
"removeViewer",
|
|
161
|
-
"removeEditor",
|
|
162
|
-
|
|
163
|
-
// these convert to a pdf so we'll need to figure out how to do that
|
|
164
|
-
// probably using the Drive export
|
|
165
|
-
"getAs",
|
|
166
155
|
"getBlob",
|
|
167
156
|
];
|
|
168
157
|
|
|
@@ -181,6 +170,36 @@ export class FakeSpreadsheet {
|
|
|
181
170
|
return this.__getFirstSheet();
|
|
182
171
|
}
|
|
183
172
|
|
|
173
|
+
addViewer(emailAddress) {
|
|
174
|
+
this.__file.addViewer(emailAddress);
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
addEditor(emailAddress) {
|
|
179
|
+
this.__file.addEditor(emailAddress);
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
addViewers(emailAddresses) {
|
|
184
|
+
this.__file.addViewers(emailAddresses);
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
addEditors(emailAddresses) {
|
|
189
|
+
this.__file.addEditors(emailAddresses);
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
removeViewer(emailAddress) {
|
|
194
|
+
this.__file.removeViewer(emailAddress);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
removeEditor(emailAddress) {
|
|
199
|
+
this.__file.removeEditor(emailAddress);
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
|
|
184
203
|
|
|
185
204
|
addDeveloperMetadata(key, value, visibility) {
|
|
186
205
|
const { nargs, matchThrow } = signatureArgs(
|
|
@@ -12,8 +12,10 @@ import { newFakeRichTextValueBuilder } from "../common/fakerichtextvalue.js";
|
|
|
12
12
|
import { newFakeTextStyleBuilder } from "../common/faketextstylebuilder.js";
|
|
13
13
|
import { newFakeFilterCriteriaBuilder } from "./fakefiltercriteriabuilder.js";
|
|
14
14
|
import { newFakeDataValidationBuilder } from "./fakedatavalidationbuilder.js";
|
|
15
|
+
import { newFakeConditionalFormatRuleBuilder } from "./fakeconditionalformatrulebuilder.js";
|
|
15
16
|
import { newFakeDataSourceSpecBuilder } from "./fakedatasourcespecbuilder.js";
|
|
16
|
-
import {
|
|
17
|
+
import { newFakeTextFinder } from "./faketextfinder.js";
|
|
18
|
+
import { newFakeCellImageBuilder } from "./fakecellimagebuilder.js";
|
|
17
19
|
|
|
18
20
|
import * as Enums from "../enums/sheetsenums.js";
|
|
19
21
|
|
|
@@ -79,7 +81,6 @@ export class FakeSpreadsheetApp {
|
|
|
79
81
|
|
|
80
82
|
const props = [
|
|
81
83
|
"getActive",
|
|
82
|
-
"newConditionalFormatRule",
|
|
83
84
|
"getActiveSheet",
|
|
84
85
|
"getCurrentCell",
|
|
85
86
|
"getActiveRange",
|
|
@@ -89,7 +90,6 @@ export class FakeSpreadsheetApp {
|
|
|
89
90
|
"setCurrentCell",
|
|
90
91
|
"setActiveRange",
|
|
91
92
|
"setActiveRangeList",
|
|
92
|
-
"newCellImage",
|
|
93
93
|
"getUi",
|
|
94
94
|
"open",
|
|
95
95
|
|
|
@@ -228,6 +228,15 @@ export class FakeSpreadsheetApp {
|
|
|
228
228
|
return this.openById(match[1]);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/**
|
|
232
|
+
* newCellImage() https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#newcellimage
|
|
233
|
+
* Creates a builder for a CellImage.
|
|
234
|
+
* @returns {FakeCellImageBuilder}
|
|
235
|
+
*/
|
|
236
|
+
newCellImage() {
|
|
237
|
+
return newFakeCellImageBuilder();
|
|
238
|
+
}
|
|
239
|
+
|
|
231
240
|
/**
|
|
232
241
|
* newColor() https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#newcolor
|
|
233
242
|
* Creates a builder for a Color.
|
|
@@ -237,6 +246,15 @@ export class FakeSpreadsheetApp {
|
|
|
237
246
|
return newFakeColorBuilder();
|
|
238
247
|
}
|
|
239
248
|
|
|
249
|
+
/**
|
|
250
|
+
* newConditionalFormatRule() https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#newconditionalformatrule
|
|
251
|
+
* Creates a builder for a conditional formatting rule.
|
|
252
|
+
* @returns {FakeConditionalFormatRuleBuilder}
|
|
253
|
+
*/
|
|
254
|
+
newConditionalFormatRule() {
|
|
255
|
+
return newFakeConditionalFormatRuleBuilder();
|
|
256
|
+
}
|
|
257
|
+
|
|
240
258
|
/**
|
|
241
259
|
* newTextStyle() https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#newTextStyle()
|
|
242
260
|
* Creates a builder for a TextStyle.
|
|
@@ -116,6 +116,37 @@ const fetchAll = (requests) => {
|
|
|
116
116
|
return responses.map(({ data }) => responsify(data))
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
const getRequest = (url, options = {}) => {
|
|
120
|
+
const defaultMethod = 'get';
|
|
121
|
+
const request = {
|
|
122
|
+
url: url,
|
|
123
|
+
method: (options.method || defaultMethod).toLowerCase(),
|
|
124
|
+
headers: options.headers || {},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Apps Script defaults
|
|
128
|
+
if (options.contentType) {
|
|
129
|
+
request.contentType = options.contentType;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (options.payload) {
|
|
133
|
+
request.payload = options.payload;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (options.useIntranet !== undefined) {
|
|
137
|
+
request.useIntranet = options.useIntranet;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Add standard fetch behavior
|
|
141
|
+
if (request.method === 'post' || request.method === 'put' || request.method === 'patch') {
|
|
142
|
+
if (!request.contentType) {
|
|
143
|
+
request.contentType = 'application/x-www-form-urlencoded';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return request;
|
|
148
|
+
}
|
|
149
|
+
|
|
119
150
|
|
|
120
151
|
// This will eventually hold a proxy for DriveApp
|
|
121
152
|
let _app = null
|
|
@@ -131,7 +162,8 @@ if (typeof globalThis[name] === typeof undefined) {
|
|
|
131
162
|
if (!_app) {
|
|
132
163
|
_app = {
|
|
133
164
|
fetch,
|
|
134
|
-
fetchAll
|
|
165
|
+
fetchAll,
|
|
166
|
+
getRequest
|
|
135
167
|
}
|
|
136
168
|
}
|
|
137
169
|
// this is the actual driveApp we'll return from the proxy
|
|
@@ -31,7 +31,9 @@ export const getPermissionIterator = ({
|
|
|
31
31
|
pageToken = nextPageToken
|
|
32
32
|
|
|
33
33
|
// format the results into the folder or file object
|
|
34
|
-
|
|
34
|
+
// we must copy the array because the cache returns a reference
|
|
35
|
+
// and splicing it would empty the cache for subsequent calls
|
|
36
|
+
tank = [...permissions]
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
// if we've got anything in the tank send back the oldest one
|
|
@@ -4,14 +4,18 @@ import { newFakeUser } from '../services/common/fakeuser.js';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* get the file sharers
|
|
7
|
-
* @
|
|
7
|
+
* @param {string} id the file id
|
|
8
|
+
* @param {string|string[]} roles the role or roles to filter by
|
|
9
|
+
* @returns {FakeUser[]} the file viewers
|
|
8
10
|
*/
|
|
9
|
-
export const getSharers = (id,
|
|
11
|
+
export const getSharers = (id, roles) => {
|
|
12
|
+
const roleList = Array.isArray(roles) ? roles : [roles]
|
|
10
13
|
const pit = getPermissionIterator({ id })
|
|
11
14
|
const viewers = []
|
|
12
15
|
while (pit.hasNext()) {
|
|
13
16
|
const permission = pit.next()
|
|
14
|
-
|
|
17
|
+
// console.log(`...DEBUG: permission for ${id}:`, JSON.stringify(permission))
|
|
18
|
+
if (roleList.includes(permission.role) && permission.type === "user") viewers.push(makeUserFromPermission(permission))
|
|
15
19
|
}
|
|
16
20
|
return viewers
|
|
17
21
|
}
|
package/src/support/peeker.js
CHANGED
|
@@ -8,13 +8,19 @@ class Peeker {
|
|
|
8
8
|
/**
|
|
9
9
|
* @constructor
|
|
10
10
|
* @param {function} generator the generator function to add a hasNext() to
|
|
11
|
+
* @param {function} [continuationHandler] a function to return a continuation token
|
|
11
12
|
* @returns {Peeker}
|
|
12
13
|
*/
|
|
13
|
-
constructor(generator) {
|
|
14
|
+
constructor(generator, continuationHandler) {
|
|
14
15
|
this.generator = generator
|
|
15
16
|
// in order to be able to do a hasnext we have to actually get the value
|
|
16
17
|
// this is the next value stored
|
|
17
18
|
this.peeked = generator.next()
|
|
19
|
+
this.continuationHandler = continuationHandler
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getContinuationToken() {
|
|
23
|
+
return this.continuationHandler ? this.continuationHandler(this.peeked) : null
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
/**
|
|
@@ -37,7 +43,7 @@ class Peeker {
|
|
|
37
43
|
// instead of returning the next, we return the prepeeked next
|
|
38
44
|
const value = this.peeked.value
|
|
39
45
|
this.peeked = this.generator.next()
|
|
40
|
-
return value
|
|
46
|
+
return value?.__fakeResolved ?? value
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
49
|
|
package/src/support/sxdrive.js
CHANGED
|
@@ -550,25 +550,36 @@ const sxStreamer = async ({
|
|
|
550
550
|
params,
|
|
551
551
|
options = {},
|
|
552
552
|
method = 'get' }) => {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
553
|
+
try {
|
|
554
|
+
// this is the node drive service
|
|
555
|
+
const drive = getDriveApiClient();
|
|
556
|
+
const streamed = await drive.files[method](params, {
|
|
557
|
+
responseType: 'stream',
|
|
558
|
+
...options
|
|
559
|
+
})
|
|
560
|
+
const response = responseSyncify(streamed)
|
|
561
|
+
if (response.status === 200) {
|
|
562
|
+
const buf = await getStreamAsBuffer(streamed.data)
|
|
563
|
+
const data = Array.from(buf)
|
|
563
564
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
565
|
+
return {
|
|
566
|
+
data,
|
|
567
|
+
response
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
return {
|
|
571
|
+
data: null,
|
|
572
|
+
response
|
|
573
|
+
}
|
|
567
574
|
}
|
|
568
|
-
}
|
|
575
|
+
} catch (err) {
|
|
576
|
+
// We don't want to crash the worker if the API call fails
|
|
577
|
+
// (e.g. exporting a non-exportable file)
|
|
578
|
+
const response = responseSyncify(err?.response || { status: err?.code || 500, statusText: err?.message })
|
|
569
579
|
return {
|
|
570
580
|
data: null,
|
|
571
|
-
response
|
|
581
|
+
response,
|
|
582
|
+
error: err?.response?.data || err?.message || err
|
|
572
583
|
}
|
|
573
584
|
}
|
|
574
585
|
}
|
package/src/support/syncit.js
CHANGED
|
@@ -70,10 +70,8 @@ const register = (id, cacher, result, allow404 = false, params) => {
|
|
|
70
70
|
const { data, response } = result;
|
|
71
71
|
|
|
72
72
|
if (checkResponseCacher(id, response, allow404, cacher)) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
data: cacher.setEntry(id, params, data),
|
|
76
|
-
};
|
|
73
|
+
cacher.setEntry(id, params, normalizeSerialization(data));
|
|
74
|
+
return result;
|
|
77
75
|
} else {
|
|
78
76
|
return result;
|
|
79
77
|
}
|
|
@@ -147,7 +145,7 @@ const fxGeneric = ({
|
|
|
147
145
|
const data = cacher.getEntry(resourceId, otherParams);
|
|
148
146
|
if (data) {
|
|
149
147
|
return {
|
|
150
|
-
data,
|
|
148
|
+
data: normalizeSerialization(data),
|
|
151
149
|
response: {
|
|
152
150
|
status: 200,
|
|
153
151
|
fromCache: true,
|
|
@@ -203,7 +201,7 @@ const fxDriveGet = ({
|
|
|
203
201
|
const { cachedFile, good } = getFromFileCache(id, params.fields);
|
|
204
202
|
if (good)
|
|
205
203
|
return {
|
|
206
|
-
data: cachedFile,
|
|
204
|
+
data: normalizeSerialization(cachedFile),
|
|
207
205
|
// fake a good sxresponse
|
|
208
206
|
response: {
|
|
209
207
|
status: 200,
|
|
@@ -101,7 +101,12 @@ function cleanup() {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// The 'exit' event is for when the process is already shutting down normally.
|
|
104
|
-
process.on('exit',
|
|
104
|
+
process.on('exit', () => {
|
|
105
|
+
// Only terminate the worker if we aren't in the middle of an interactive CLI session
|
|
106
|
+
if (!process.env.GF_CLI_INTERACTIVE) {
|
|
107
|
+
cleanup();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
105
110
|
// By not listening for 'SIGINT', we allow Node.js to perform its default action,
|
|
106
111
|
// which is to exit the process. The 'exit' event will then be fired to clean up the worker.
|
|
107
112
|
process.on('SIGTERM', cleanup); // Catches `kill`
|
|
@@ -181,10 +186,25 @@ export function callSync(method, ...args) {
|
|
|
181
186
|
|
|
182
187
|
if (hasError) {
|
|
183
188
|
// Re-hydrate the error object on the main thread.
|
|
184
|
-
|
|
189
|
+
// The error message from the worker might be a simple string or a JSON object from an API response
|
|
190
|
+
let message = resultData.message || resultString || "Unknown worker error";
|
|
191
|
+
|
|
192
|
+
// If it's a Drive API error object, the message is often nested
|
|
193
|
+
if (resultData.error?.message) {
|
|
194
|
+
message = resultData.error.message;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const err = new Error(message);
|
|
185
198
|
err.stack = resultData.stack;
|
|
186
|
-
|
|
187
|
-
|
|
199
|
+
|
|
200
|
+
// Copy other important properties if they exist, except the ones we've handled
|
|
201
|
+
const skip = ['message', 'stack', 'name'];
|
|
202
|
+
Object.keys(resultData).forEach(key => {
|
|
203
|
+
if (!skip.includes(key)) {
|
|
204
|
+
err[key] = resultData[key];
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
188
208
|
throw err;
|
|
189
209
|
}
|
|
190
210
|
|
|
@@ -54,11 +54,23 @@ async function writeResult(result) {
|
|
|
54
54
|
* @param {Error} error The error to write.
|
|
55
55
|
*/
|
|
56
56
|
function writeError(error) {
|
|
57
|
-
|
|
57
|
+
let errorObject = {
|
|
58
58
|
message: error.message,
|
|
59
59
|
stack: error.stack,
|
|
60
60
|
name: error.name,
|
|
61
61
|
};
|
|
62
|
+
|
|
63
|
+
// If the message is a JSON string (common for API errors that have been caught and re-thrown),
|
|
64
|
+
// try to parse it and merge the properties for better re-hydration on the main thread.
|
|
65
|
+
if (error.message && error.message.startsWith('{') && error.message.endsWith('}')) {
|
|
66
|
+
try {
|
|
67
|
+
const parsedMessage = JSON.parse(error.message);
|
|
68
|
+
errorObject = { ...errorObject, ...parsedMessage };
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// Not valid JSON, keep as is
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
const errorString = JSON.stringify(errorObject);
|
|
63
75
|
const encodedError = textEncoder.encode(errorString);
|
|
64
76
|
|
|
@@ -142,7 +154,6 @@ parentPort.on('message', async (task) => {
|
|
|
142
154
|
await writeResult(result);
|
|
143
155
|
|
|
144
156
|
} catch (error) {
|
|
145
|
-
syncError('An unhandled error occurred in the worker', error);
|
|
146
157
|
writeError(error);
|
|
147
158
|
} finally {
|
|
148
159
|
// 3. Signal completion and wake up the main thread.
|
package/summarize_advanced.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import './main.js';
|
|
2
|
-
|
|
3
|
-
try {
|
|
4
|
-
console.log('Searching for emails from Martin...');
|
|
5
|
-
|
|
6
|
-
// Search Gmail for messages from Martin (getting up to 10 recent threads)
|
|
7
|
-
const threads = GmailApp.search('from:Martin', 0, 10);
|
|
8
|
-
|
|
9
|
-
if (threads.length === 0) {
|
|
10
|
-
console.log('No emails found from Martin.');
|
|
11
|
-
process.exit(0);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
console.log(`Found ${threads.length} threads. Creating summary doc...`);
|
|
15
|
-
|
|
16
|
-
// Create the Document
|
|
17
|
-
const doc = DocumentApp.create('Email Summary: Martin');
|
|
18
|
-
const body = doc.getBody();
|
|
19
|
-
|
|
20
|
-
// Add a title
|
|
21
|
-
body.appendParagraph('Summary of Recent Emails from Martin')
|
|
22
|
-
.setHeading(DocumentApp.ParagraphHeading.TITLE);
|
|
23
|
-
|
|
24
|
-
body.appendParagraph(`Generated on: ${new Date().toLocaleString()}`);
|
|
25
|
-
body.appendParagraph('');
|
|
26
|
-
|
|
27
|
-
// We have to use the advanced Gmail service directly because gas-fakes'
|
|
28
|
-
// FakeGmailMessage does not yet support getSubject(), getDate(), or getSnippet()
|
|
29
|
-
for (const thread of threads) {
|
|
30
|
-
// Get the raw thread resource from the Gmail API
|
|
31
|
-
const threadResource = Gmail.Users.Threads.get('me', thread.getId());
|
|
32
|
-
if (!threadResource.messages || threadResource.messages.length === 0) continue;
|
|
33
|
-
|
|
34
|
-
const firstMsg = threadResource.messages[0];
|
|
35
|
-
|
|
36
|
-
// Extract Subject from headers
|
|
37
|
-
const headers = firstMsg.payload && firstMsg.payload.headers ? firstMsg.payload.headers : [];
|
|
38
|
-
const subjectHeader = headers.find(h => h.name.toLowerCase() === 'subject');
|
|
39
|
-
const dateHeader = headers.find(h => h.name.toLowerCase() === 'date');
|
|
40
|
-
|
|
41
|
-
const subject = subjectHeader ? subjectHeader.value : 'No Subject';
|
|
42
|
-
const date = dateHeader ? dateHeader.value : 'Unknown Date';
|
|
43
|
-
|
|
44
|
-
body.appendParagraph(`Subject: ${subject}`)
|
|
45
|
-
.setHeading(DocumentApp.ParagraphHeading.HEADING1);
|
|
46
|
-
|
|
47
|
-
for (const msg of threadResource.messages) {
|
|
48
|
-
const msgHeaders = msg.payload && msg.payload.headers ? msg.payload.headers : [];
|
|
49
|
-
const msgDateHeader = msgHeaders.find(h => h.name.toLowerCase() === 'date');
|
|
50
|
-
const msgDate = msgDateHeader ? msgDateHeader.value : 'Unknown Date';
|
|
51
|
-
|
|
52
|
-
body.appendParagraph(`Date: ${msgDate}`)
|
|
53
|
-
.setHeading(DocumentApp.ParagraphHeading.HEADING3);
|
|
54
|
-
|
|
55
|
-
const snippet = msg.snippet || "No content.";
|
|
56
|
-
body.appendParagraph(snippet);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
body.appendParagraph(''); // Spacing
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
doc.saveAndClose();
|
|
63
|
-
|
|
64
|
-
console.log(`Successfully created document "${doc.getName()}"`);
|
|
65
|
-
console.log(`Document URL: ${doc.getUrl()}`);
|
|
66
|
-
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error(`Error: ${error.message}`);
|
|
69
|
-
}
|