@mcpher/gas-fakes 1.0.21 → 1.0.22
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 +16 -3
- package/gprompts/gas-inventory.js +71 -67
- package/package.json +3 -2
- package/src/services/logger/fakelogger.js +51 -7
- package/src/services/spreadsheetapp/fakeovergridimage.js +47 -0
- package/src/services/spreadsheetapp/fakesheet.js +29 -1
- package/src/support/sxxlsx.js +41 -0
- package/src/support/syncit.js +179 -141
- package/src/support/workersync/sxfunctions.js +11 -10
package/README.md
CHANGED
|
@@ -120,7 +120,7 @@ When logging to the cloud, entries are sent to the `gas-fakes/console_logs` log
|
|
|
120
120
|
|
|
121
121
|
#### Dynamic Control
|
|
122
122
|
|
|
123
|
-
You can change the logging destination at any time during runtime
|
|
123
|
+
You can change the logging destination at any time during runtime by setting the `Logger.__logDestination` property. This is especially useful for testing or for applications that need to change their logging behavior dynamically.
|
|
124
124
|
|
|
125
125
|
The method accepts one of the following string values: `'CONSOLE'`, `'CLOUD'`, `'BOTH'`, or `'NONE'`.
|
|
126
126
|
|
|
@@ -137,13 +137,26 @@ To change the destination during runtime in your script:
|
|
|
137
137
|
Logger.log('This goes to console and cloud');
|
|
138
138
|
|
|
139
139
|
// Switch to only logging to the console
|
|
140
|
-
Logger.
|
|
140
|
+
Logger.__logDestination='CONSOLE';
|
|
141
141
|
Logger.log('This now only goes to the console');
|
|
142
142
|
|
|
143
143
|
// Disable logging completely
|
|
144
|
-
Logger.
|
|
144
|
+
Logger.__logDestination='NONE';
|
|
145
145
|
Logger.log('This goes nowhere');
|
|
146
146
|
```
|
|
147
|
+
|
|
148
|
+
#### Link to Cloud log for this run
|
|
149
|
+
|
|
150
|
+
If you have used Logging to cloud, you can get a link to the log data like this.
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
console.log ('....example cloud log link for this session',Logger.__cloudLogLink)
|
|
154
|
+
````
|
|
155
|
+
|
|
156
|
+
It contains a cloud logging query that will display any logging done in this session - the filter is based on the scriptId (from gasfakes.json), the projectId and userId (from Auth), as well as the start and end time of the session.
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
147
160
|
### Pushing files to GAS
|
|
148
161
|
|
|
149
162
|
There are a couple of syntactical differences between Node and Apps Script. Not in the body of the code but in how the IDE executes. The 2 main ones are
|
|
@@ -3,90 +3,94 @@ import * as cheerio from 'cheerio';
|
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import { URL } from 'url';
|
|
5
5
|
|
|
6
|
-
const baseUrl = "https://developers.google.com/apps-script/reference/"
|
|
6
|
+
const baseUrl = "https://developers.google.com/apps-script/reference/script/"
|
|
7
7
|
const outputFile = './gas-inventory.json';
|
|
8
8
|
|
|
9
9
|
const visited = new Map();
|
|
10
10
|
const queue = [baseUrl]
|
|
11
11
|
|
|
12
|
+
// the urls are kebab cased
|
|
12
13
|
const kebabCamel = (s) => {
|
|
13
14
|
return s.replace(/([-][a-z])/ig, ($1) => {
|
|
14
15
|
return $1.toUpperCase()
|
|
15
16
|
.replace('-', '')
|
|
16
17
|
});
|
|
17
18
|
};
|
|
18
|
-
async function scrape() {
|
|
19
|
-
const inventory = {};
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
console.log(`Scraping ${url}`);
|
|
24
|
-
const response = await got(url);
|
|
25
|
-
const $ = cheerio.load(response.body);
|
|
26
|
-
const serviceName = $('h1').text().split('\n').map(f=>f.replace(/^\s+/,'').trim()).filter (f=>f)[0];
|
|
27
|
-
console.log ('...starting service name',serviceName)
|
|
28
|
-
inventory[serviceName] = {
|
|
29
|
-
name: serviceName,
|
|
30
|
-
link: url,
|
|
31
|
-
classes: {},
|
|
32
|
-
};
|
|
20
|
+
function debugPageStructure($) {
|
|
21
|
+
console.log('=== PAGE STRUCTURE DEBUG ===');
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const method = kebabCamel(absoluteUrl.match(/#(.*)/) || ['', ''][1])
|
|
40
|
-
if (!visited.has(parentUrl)) {
|
|
41
|
-
console.log ('...adding to queue',parentUrl)
|
|
42
|
-
queue.push(parentUrl);
|
|
43
|
-
visited.set(parentUrl, {
|
|
44
|
-
url: parentUrl,
|
|
45
|
-
methods: new Map ()
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
if (method) {
|
|
49
|
-
visited.get(parentUrl).methods.set(absoluteUrl, {
|
|
50
|
-
url: absoluteUrl,
|
|
51
|
-
method
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
});
|
|
23
|
+
// Check if main elements exist
|
|
24
|
+
console.log('#main-content exists:', $('#main-content').length > 0);
|
|
25
|
+
console.log('devsite-content exists:', $('devsite-content').length > 0);
|
|
26
|
+
console.log('article exists:', $('article').length > 0);
|
|
27
|
+
console.log('h1 elements:', $('h1').length);
|
|
56
28
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
console.log ('...found class',className,'for service',serviceName)
|
|
62
|
-
inventory[serviceName].classes[className] = {
|
|
63
|
-
methods: {},
|
|
64
|
-
properties: {},
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Find the table of methods for the current class
|
|
68
|
-
const methodsTable = $(el).nextAll('table').first();
|
|
69
|
-
methodsTable.find('tbody tr').each((j, row) => {
|
|
70
|
-
const methodName = $(row).find('td').eq(1).text().trim();
|
|
71
|
-
const methodLink = $(row).find('td').eq(1).find('a').attr('href');
|
|
72
|
-
const returnType = $(row).find('td').eq(0).text().trim();
|
|
29
|
+
// Show all h1 texts
|
|
30
|
+
$('h1').each((i, el) => {
|
|
31
|
+
console.log(`H1[${i}]:`, $(el).text().trim());
|
|
32
|
+
});
|
|
73
33
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
}
|
|
34
|
+
// Show structure of main-content
|
|
35
|
+
const mainContent = $('#main-content');
|
|
36
|
+
if (mainContent.length > 0) {
|
|
37
|
+
console.log('Main-content children:', mainContent.children().length);
|
|
38
|
+
mainContent.children().each((i, el) => {
|
|
39
|
+
console.log(`Child ${i}:`, el.tagName, $(el).attr('class') || $(el).attr('id'));
|
|
85
40
|
});
|
|
86
41
|
}
|
|
42
|
+
}
|
|
87
43
|
|
|
88
|
-
|
|
89
|
-
|
|
44
|
+
|
|
45
|
+
const getNextCheer = async (u) => {
|
|
46
|
+
console.log(`Scraping ${u}`);
|
|
47
|
+
const response = await got(u);
|
|
48
|
+
return cheerio.load(response.body);
|
|
90
49
|
}
|
|
91
50
|
|
|
92
|
-
|
|
51
|
+
const getHrefs = ($) => {
|
|
52
|
+
return $('a').map((i, el) => {
|
|
53
|
+
const r = $(el).attr('href');
|
|
54
|
+
if (r) {
|
|
55
|
+
const absolute = new URL(r, baseUrl).href;
|
|
56
|
+
// skip references to elsewhere and blank anchors
|
|
57
|
+
if (absolute.startsWith(baseUrl) && !absolute.endsWith('#')) return absolute;
|
|
58
|
+
}
|
|
59
|
+
}).get();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
const s2 = async () => {
|
|
64
|
+
const umap = new Map();
|
|
65
|
+
const uset = new Set()
|
|
66
|
+
while (queue.length) {
|
|
67
|
+
|
|
68
|
+
const url = queue.shift();
|
|
69
|
+
console.log('working on url', url)
|
|
70
|
+
const $ = await getNextCheer(url);
|
|
71
|
+
debugPageStructure($);
|
|
72
|
+
|
|
73
|
+
// get all the hrefs
|
|
74
|
+
getHrefs($).forEach(f => {
|
|
75
|
+
// so a url could have an anchor for a method so we need to strip that out
|
|
76
|
+
const u = new URL(f);
|
|
77
|
+
const method = u.hash.substring(1);
|
|
78
|
+
const classUrl = u.origin + '/' + u.pathname
|
|
79
|
+
const methodUrl = u.href
|
|
80
|
+
if (!umap.has(classUrl)) {
|
|
81
|
+
umap.set(classUrl, {
|
|
82
|
+
parents: new Map(),
|
|
83
|
+
classUrl,
|
|
84
|
+
methods: new Map()
|
|
85
|
+
})
|
|
86
|
+
console.log ('pushing', classUrl)
|
|
87
|
+
queue.push (classUrl)
|
|
88
|
+
}
|
|
89
|
+
const ob = umap.get(classUrl)
|
|
90
|
+
ob.methods.set(method, { method, methodUrl })
|
|
91
|
+
ob.parents.set(url, { url })
|
|
92
|
+
console.log('adding', f)
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
s2().catch(console.error)
|
package/package.json
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@google-cloud/logging": "^11.2.1",
|
|
7
7
|
"@mcpher/fake-gasenum": "^1.0.2",
|
|
8
|
-
"@mcpher/unit": "^1.1.11",
|
|
9
8
|
"@sindresorhus/is": "^7.0.1",
|
|
10
9
|
"archiver": "^7.0.1",
|
|
10
|
+
"exceljs": "^4.4.0",
|
|
11
11
|
"fast-json-stable-stringify": "^2.1.0",
|
|
12
12
|
"get-stream": "^9.0.1",
|
|
13
13
|
"googleapis": "^157.0.0",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"keyv": "^5.5.0",
|
|
18
18
|
"keyv-file": "^5.1.3",
|
|
19
19
|
"mime": "^4.0.7",
|
|
20
|
+
"sinon": "^21.0.0",
|
|
20
21
|
"sleep-synchronously": "^2.0.0",
|
|
21
22
|
"subsume": "^4.0.0",
|
|
22
23
|
"to-readable-stream": "^4.0.0",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"pub": "cp mainlocal.js main.js && npm publish --access public"
|
|
65
66
|
},
|
|
66
67
|
"name": "@mcpher/gas-fakes",
|
|
67
|
-
"version": "1.0.
|
|
68
|
+
"version": "1.0.22",
|
|
68
69
|
"license": "MIT",
|
|
69
70
|
"main": "main.js",
|
|
70
71
|
"description": "A proof of concept implementation of Apps Script Environment on Node",
|
|
@@ -23,6 +23,12 @@ export class FakeLogger {
|
|
|
23
23
|
* @type {string}
|
|
24
24
|
*/
|
|
25
25
|
this.__destination = (process.env.LOG_DESTINATION || 'CONSOLE').toUpperCase();
|
|
26
|
+
this.__startedLoggingAt = new Date()
|
|
27
|
+
this.__cloudLogLink = 'No cloud logging enabled - use Logger.__logDestination = "CLOUD" or "BOTH" to enable'
|
|
28
|
+
|
|
29
|
+
if (['CLOUD','BOTH'].includes(this.__destination)) {
|
|
30
|
+
writeToCloudOrConsole("...Initializing cloud logging", this)
|
|
31
|
+
}
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
/**
|
|
@@ -58,6 +64,7 @@ export class FakeLogger {
|
|
|
58
64
|
const message = format(formatOrData, ...values);
|
|
59
65
|
this.__log.push(message);
|
|
60
66
|
|
|
67
|
+
|
|
61
68
|
// Pass the destination and instance context to the writer function.
|
|
62
69
|
writeToCloudOrConsole(message, this);
|
|
63
70
|
|
|
@@ -83,6 +90,12 @@ export class FakeLogger {
|
|
|
83
90
|
}
|
|
84
91
|
return this;
|
|
85
92
|
}
|
|
93
|
+
|
|
94
|
+
// to allow code compatibility without error on apps script, we should use the setter since it'll be undefined on Apps live Script
|
|
95
|
+
set __logDestination (destination) {
|
|
96
|
+
return this.__setLogDestination(destination);
|
|
97
|
+
}
|
|
98
|
+
|
|
86
99
|
}
|
|
87
100
|
|
|
88
101
|
/**
|
|
@@ -96,21 +109,23 @@ export const newFakeLogger = (...args) => Proxies.guard(new FakeLogger(...args))
|
|
|
96
109
|
* @private
|
|
97
110
|
* @param {FakeLogger} loggerInstance The logger instance, to access its destination.
|
|
98
111
|
*/
|
|
99
|
-
const writeToCloudOrConsole =
|
|
112
|
+
const writeToCloudOrConsole = (message, loggerInstance) => {
|
|
100
113
|
const logDestination = loggerInstance.__destination;
|
|
101
114
|
const useConsoleLogging = logDestination === 'CONSOLE' || logDestination === 'BOTH';
|
|
102
115
|
const useCloudLogging = logDestination === 'CLOUD' || logDestination === 'BOTH';
|
|
116
|
+
const logName = 'gas-fakes/console_logs';
|
|
117
|
+
const projectId = ScriptApp.__projectId;
|
|
118
|
+
const scriptId = ScriptApp.getScriptId();
|
|
119
|
+
const userId = ScriptApp.__userId;
|
|
103
120
|
|
|
104
121
|
// Lazy-initialize the cloud logger if needed.
|
|
105
122
|
const initializeCloudLogging = () => {
|
|
106
123
|
if (cloudLog) return; // Already initialized.
|
|
107
124
|
try {
|
|
108
|
-
const projectId = ScriptApp.__projectId;
|
|
109
125
|
if (!projectId) {
|
|
110
126
|
throw new Error('Could not determine Google Cloud Project ID for logging.');
|
|
111
127
|
}
|
|
112
128
|
const logging = new Logging({ projectId });
|
|
113
|
-
const logName = 'gas-fakes/console_logs';
|
|
114
129
|
cloudLog = logging.log(logName);
|
|
115
130
|
console.info(`gas-fakes: Cloud Logging is enabled, writing to log "${logName}".`);
|
|
116
131
|
} catch (err) {
|
|
@@ -145,8 +160,8 @@ const writeToCloudOrConsole = (message, loggerInstance) => {
|
|
|
145
160
|
// not running on a specific GCP compute service.
|
|
146
161
|
resource: { type: 'global' },
|
|
147
162
|
labels: {
|
|
148
|
-
'gas-fakes-scriptId':
|
|
149
|
-
'gas-fakes-userId':
|
|
163
|
+
'gas-fakes-scriptId': scriptId,
|
|
164
|
+
'gas-fakes-userId': userId,
|
|
150
165
|
},
|
|
151
166
|
jsonPayload: { message: message },
|
|
152
167
|
severity: 'INFO',
|
|
@@ -156,7 +171,36 @@ const writeToCloudOrConsole = (message, loggerInstance) => {
|
|
|
156
171
|
// his allows us to catch errors, such as a disabled API or permission issues.
|
|
157
172
|
// however we are firing and forgetting so an error might appear later on in execution
|
|
158
173
|
if (cloudLog) {
|
|
159
|
-
|
|
174
|
+
// filter on user/script/priject and run times
|
|
175
|
+
// we'll extend the end date filter to a little more than now
|
|
176
|
+
const aLittleMore = 7 * 1000
|
|
177
|
+
const endDate = new Date(new Date().getTime() + aLittleMore)
|
|
178
|
+
const startDate = loggerInstance.__startedLoggingAt
|
|
179
|
+
|
|
180
|
+
const base = `https://console.cloud.google.com/logs/query;`
|
|
181
|
+
|
|
182
|
+
// B. Build the RAW LQL Filter String (NO "query=" prefix here)
|
|
183
|
+
const lqlParts = [
|
|
184
|
+
// LogName filter
|
|
185
|
+
`logName="projects/${projectId}/logs/${logName.replace('/', '%2F')}"`,
|
|
186
|
+
// Labels filter
|
|
187
|
+
...Object.keys(metadata.labels).map(k => `jsonPayload.labels.${k}="${metadata.labels[k]}"`),
|
|
188
|
+
// Date filter (recommended for query stability)
|
|
189
|
+
`timestamp>="${startDate.toISOString()}"`,
|
|
190
|
+
`timestamp<="${endDate.toISOString()}"`
|
|
191
|
+
];
|
|
192
|
+
const fullLQLQuery = lqlParts.join(' AND ');
|
|
193
|
+
|
|
194
|
+
// C. Encode ONLY the filter content, then prepend the literal 'query='
|
|
195
|
+
const encodedQueryParam = `query=${encodeURIComponent(fullLQLQuery)}`;
|
|
196
|
+
|
|
197
|
+
// D. Create the timeRange parameter (correct casing)
|
|
198
|
+
const timeRangeParam = `;timeRange=${startDate.getTime()}/${endDate.getTime()}`;
|
|
199
|
+
|
|
200
|
+
// E. Assemble the final URL
|
|
201
|
+
loggerInstance.__cloudLogLink = base + encodedQueryParam + `?project=${projectId}`;
|
|
202
|
+
// fire and forget
|
|
203
|
+
cloudLog.write(cloudLog.entry(metadata)).catch(err => console.error('gas-fakes: Failed to write to Cloud Logging.', err.message))
|
|
160
204
|
}
|
|
161
205
|
}
|
|
162
|
-
};
|
|
206
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Proxies } from "../../support/proxies.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* create a new FakeOverGridImage instance
|
|
5
|
+
* @param {...any} args
|
|
6
|
+
* @returns {FakeOverGridImage}
|
|
7
|
+
*/
|
|
8
|
+
export const newFakeOverGridImage = (...args) => {
|
|
9
|
+
return Proxies.guard(new FakeOverGridImage(...args));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* basic fake FakeOverGridImage
|
|
14
|
+
* @class FakeOverGridImage
|
|
15
|
+
*/
|
|
16
|
+
export class FakeOverGridImage {
|
|
17
|
+
/**
|
|
18
|
+
* @constructor
|
|
19
|
+
* @param {Sheet} sheet
|
|
20
|
+
* @param {Object} obj
|
|
21
|
+
* @returns {FakeOverGridImage}
|
|
22
|
+
*/
|
|
23
|
+
constructor(sheet, obj) {
|
|
24
|
+
this.sheet = sheet;
|
|
25
|
+
this.object = obj;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getAnchorCell() {
|
|
29
|
+
return this.sheet.getRange(this.object.row + 1, this.object.col + 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getAnchorCellXOffset() {
|
|
33
|
+
return this.object.anchorCellXOffset;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getAnchorCellYOffset() {
|
|
37
|
+
return this.object.anchorCellYOffset;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getWidth() {
|
|
41
|
+
return this.object.width;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getHeight() {
|
|
45
|
+
return this.object.height;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -13,6 +13,10 @@ import { FakeTextFinder, newFakeTextFinder } from "./faketextfinder.js";
|
|
|
13
13
|
|
|
14
14
|
import { newFakeNamedRange } from "./fakenamedrange.js";
|
|
15
15
|
import { newFakeProtection } from "./fakeprotection.js";
|
|
16
|
+
import { newFakeOverGridImage } from "./fakeovergridimage.js";
|
|
17
|
+
|
|
18
|
+
import { Syncit } from "../../support/syncit.js";
|
|
19
|
+
// import ExcelJS from "exceljs"; // When test_getImages() is used, use this.
|
|
16
20
|
|
|
17
21
|
const { is, isEnum } = Utils;
|
|
18
22
|
|
|
@@ -35,7 +39,7 @@ export class FakeSheet {
|
|
|
35
39
|
"insertChart",
|
|
36
40
|
"removeChart",
|
|
37
41
|
"updateChart",
|
|
38
|
-
"getImages",
|
|
42
|
+
// "getImages",
|
|
39
43
|
"insertImage",
|
|
40
44
|
"removeImage",
|
|
41
45
|
// "getNamedRanges",
|
|
@@ -1063,6 +1067,30 @@ export class FakeSheet {
|
|
|
1063
1067
|
return [];
|
|
1064
1068
|
}
|
|
1065
1069
|
|
|
1070
|
+
// This function is used for testing getImages without a worker.
|
|
1071
|
+
// async test_getImages() {
|
|
1072
|
+
// const url = `https://docs.google.com/spreadsheets/export?exportFormat=xlsx&id=${
|
|
1073
|
+
// this.__parent.__meta.spreadsheetId
|
|
1074
|
+
// }&access_token=${ScriptApp.getOAuthToken()}`;
|
|
1075
|
+
// const res = UrlFetchApp.fetch(url);
|
|
1076
|
+
// const buf = new Uint8Array(res.getBlob()._data).buffer;
|
|
1077
|
+
// const workbook = new ExcelJS.Workbook();
|
|
1078
|
+
// await workbook.xlsx.load(buf);
|
|
1079
|
+
// const worksheet = workbook.worksheets[this.getIndex() - 1];
|
|
1080
|
+
// const images = worksheet.getImages();
|
|
1081
|
+
// console.log(images);
|
|
1082
|
+
// }
|
|
1083
|
+
|
|
1084
|
+
getImages() {
|
|
1085
|
+
const url = `https://docs.google.com/spreadsheets/export?exportFormat=xlsx&id=${
|
|
1086
|
+
this.__parent.__meta.spreadsheetId
|
|
1087
|
+
}&access_token=${ScriptApp.getOAuthToken()}`;
|
|
1088
|
+
const idx = this.getIndex() - 1;
|
|
1089
|
+
const ar = Syncit.fxGetImagesFromXlsx({ url, idx });
|
|
1090
|
+
const sheet = this;
|
|
1091
|
+
return ar.map((e) => newFakeOverGridImage(sheet, e));
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1066
1094
|
__batchUpdate({ spreadsheetId, requests }) {
|
|
1067
1095
|
return Sheets.Spreadsheets.batchUpdate({ requests }, spreadsheetId);
|
|
1068
1096
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import got from "got";
|
|
2
|
+
import ExcelJS from "exceljs";
|
|
3
|
+
|
|
4
|
+
async function __getWorkbook(url) {
|
|
5
|
+
const res = await got(url, { responseType: "buffer" });
|
|
6
|
+
let buf;
|
|
7
|
+
if (res.rawBody && Buffer.isBuffer(res.rawBody)) {
|
|
8
|
+
buf = res.rawBody;
|
|
9
|
+
} else if (res.body && Buffer.isBuffer(res.body)) {
|
|
10
|
+
buf = res.body;
|
|
11
|
+
} else {
|
|
12
|
+
throw new Error(res);
|
|
13
|
+
}
|
|
14
|
+
const workbook = new ExcelJS.Workbook();
|
|
15
|
+
await workbook.xlsx.load(buf);
|
|
16
|
+
return workbook;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const sxGetImagesFromXlsx = async (_, { url, idx }) => {
|
|
20
|
+
const workbook = await __getWorkbook(url);
|
|
21
|
+
const worksheet = workbook.worksheets[idx];
|
|
22
|
+
const ar = worksheet.getImages().reduce((arr, image) => {
|
|
23
|
+
const imageId = image.imageId;
|
|
24
|
+
if (image.range.tl.nativeColOff != 0 && image.range.tl.nativeRowOff != 0) {
|
|
25
|
+
const col = image.range.tl.nativeCol;
|
|
26
|
+
const row = image.range.tl.nativeRow;
|
|
27
|
+
const { width, height } = image.range.ext;
|
|
28
|
+
arr.push({
|
|
29
|
+
imageId,
|
|
30
|
+
row,
|
|
31
|
+
col,
|
|
32
|
+
width,
|
|
33
|
+
height,
|
|
34
|
+
anchorCellXOffset: image.range.tl.nativeColOff,
|
|
35
|
+
anchorCellYOffset: image.range.tl.nativeRowOff,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return arr;
|
|
39
|
+
}, []);
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
package/src/support/syncit.js
CHANGED
|
@@ -1,49 +1,54 @@
|
|
|
1
1
|
// this one is locally patched for now
|
|
2
2
|
// import makeSynchronous from 'make-synchronous';
|
|
3
|
-
import path from
|
|
4
|
-
import { Auth } from "./auth.js"
|
|
5
|
-
import { randomUUID } from
|
|
6
|
-
import mime from
|
|
7
|
-
import { minFields } from
|
|
8
|
-
import { mergeParamStrings } from
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { Auth } from "./auth.js";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import mime from "mime";
|
|
7
|
+
import { minFields } from "./helpers.js";
|
|
8
|
+
import { mergeParamStrings } from "./utils.js";
|
|
9
|
+
import {
|
|
10
|
+
improveFileCache,
|
|
11
|
+
checkResponse,
|
|
12
|
+
getFromFileCache,
|
|
13
|
+
} from "./filecache.js";
|
|
14
|
+
import { checkResponseCacher } from "./fetchcacher.js";
|
|
15
|
+
import { docsCacher } from "./docscacher.js";
|
|
16
|
+
import { gmailCacher } from "./gmailcacher.js";
|
|
17
|
+
import { formsCacher } from "./formscacher.js";
|
|
18
|
+
import { slidesCacher } from "./slidescacher.js";
|
|
19
|
+
import { sheetsCacher } from "./sheetscacher.js";
|
|
20
|
+
import is from "@sindresorhus/is";
|
|
21
|
+
import { callSync } from "./workersync/synchronizer.js";
|
|
22
|
+
|
|
23
|
+
const manifestDefaultPath = "./appsscript.json";
|
|
24
|
+
const claspDefaultPath = "./.clasp.json";
|
|
25
|
+
const settingsDefaultPath = "./gasfakes.json";
|
|
26
|
+
const propertiesDefaultPath = "/tmp/gas-fakes/properties";
|
|
27
|
+
const cacheDefaultPath = "/tmp/gas-fakes/cache";
|
|
25
28
|
// note that functions like Sheets.newGridRange() etc create objects that contain get and set functions
|
|
26
29
|
// the makesynchronous functions need data that can be serialized. so we need to string/parse to normlaize them
|
|
27
|
-
const normalizeSerialization = (ob) =>
|
|
28
|
-
|
|
30
|
+
const normalizeSerialization = (ob) =>
|
|
31
|
+
is.nullOrUndefined(ob) || !is.object(ob)
|
|
32
|
+
? ob
|
|
33
|
+
: JSON.parse(JSON.stringify(ob));
|
|
29
34
|
|
|
30
35
|
/**
|
|
31
36
|
* check and register a result in cache
|
|
32
37
|
* @param {import('./sxdrive.js').SxResult} the result of a sync api call
|
|
33
|
-
* @return {import('./sxdrive.js').SxResult}
|
|
38
|
+
* @return {import('./sxdrive.js').SxResult}
|
|
34
39
|
*/
|
|
35
40
|
const registerSx = (result, allow404 = false, fields) => {
|
|
36
|
-
const { data, response } = result
|
|
37
|
-
checkResponse(data?.id, response, allow404)
|
|
41
|
+
const { data, response } = result;
|
|
42
|
+
checkResponse(data?.id, response, allow404);
|
|
38
43
|
if (data?.id) {
|
|
39
44
|
return {
|
|
40
45
|
...result,
|
|
41
|
-
data: improveFileCache(data.id, data, fields)
|
|
42
|
-
}
|
|
46
|
+
data: improveFileCache(data.id, data, fields),
|
|
47
|
+
};
|
|
43
48
|
} else {
|
|
44
|
-
return result
|
|
49
|
+
return result;
|
|
45
50
|
}
|
|
46
|
-
}
|
|
51
|
+
};
|
|
47
52
|
|
|
48
53
|
const register = (id, cacher, result, allow404 = false, params) => {
|
|
49
54
|
const { data, response } = result;
|
|
@@ -56,8 +61,7 @@ const register = (id, cacher, result, allow404 = false, params) => {
|
|
|
56
61
|
} else {
|
|
57
62
|
return result;
|
|
58
63
|
}
|
|
59
|
-
}
|
|
60
|
-
|
|
64
|
+
};
|
|
61
65
|
|
|
62
66
|
/**
|
|
63
67
|
* sync a call to Drive api to stream a download
|
|
@@ -71,21 +75,28 @@ const register = (id, cacher, result, allow404 = false, params) => {
|
|
|
71
75
|
* @param {object} [p.params] any extra params
|
|
72
76
|
* @return {import('./sxdrive.js').SxResult} from the drive api
|
|
73
77
|
*/
|
|
74
|
-
const fxStreamUpMedia = ({
|
|
78
|
+
const fxStreamUpMedia = ({
|
|
79
|
+
file = {},
|
|
80
|
+
blob,
|
|
81
|
+
fields = "",
|
|
82
|
+
method = "create",
|
|
83
|
+
fileId,
|
|
84
|
+
params = {},
|
|
85
|
+
}) => {
|
|
75
86
|
// merge the required fields with the minimum
|
|
76
|
-
fields = mergeParamStrings(minFields, fields)
|
|
77
|
-
const result = callSync(
|
|
87
|
+
fields = mergeParamStrings(minFields, fields);
|
|
88
|
+
const result = callSync("sxStreamUpMedia", {
|
|
78
89
|
resource: file,
|
|
79
90
|
bytes: blob ? blob.getBytes() : null,
|
|
80
91
|
fields,
|
|
81
92
|
method,
|
|
82
93
|
mimeType: file.mimeType || blob?.getContentType(),
|
|
83
94
|
fileId,
|
|
84
|
-
params
|
|
85
|
-
})
|
|
95
|
+
params,
|
|
96
|
+
});
|
|
86
97
|
// check result and register in cache
|
|
87
|
-
return registerSx(result, false, fields)
|
|
88
|
-
}
|
|
98
|
+
return registerSx(result, false, fields);
|
|
99
|
+
};
|
|
89
100
|
|
|
90
101
|
/**
|
|
91
102
|
* sync a call to Drive api
|
|
@@ -96,29 +107,37 @@ const fxStreamUpMedia = ({ file = {}, blob, fields = "", method = "create", file
|
|
|
96
107
|
* @return {DriveResponse} from the drive api
|
|
97
108
|
*/
|
|
98
109
|
const fxDrive = ({ prop, method, params, options }) => {
|
|
99
|
-
return callSync(
|
|
110
|
+
return callSync("sxDrive", {
|
|
100
111
|
prop,
|
|
101
112
|
method,
|
|
102
113
|
params: normalizeSerialization(params),
|
|
103
|
-
options: normalizeSerialization(options)
|
|
114
|
+
options: normalizeSerialization(options),
|
|
104
115
|
});
|
|
105
|
-
}
|
|
116
|
+
};
|
|
106
117
|
|
|
107
|
-
const fxGeneric = ({
|
|
118
|
+
const fxGeneric = ({
|
|
119
|
+
serviceName,
|
|
120
|
+
prop,
|
|
121
|
+
subProp,
|
|
122
|
+
method,
|
|
123
|
+
params,
|
|
124
|
+
options,
|
|
125
|
+
cacher,
|
|
126
|
+
idField,
|
|
127
|
+
}) => {
|
|
108
128
|
const { [idField]: resourceId, ...otherParams } = params;
|
|
109
129
|
|
|
110
|
-
if (method ===
|
|
130
|
+
if (method === "get") {
|
|
111
131
|
const data = cacher.getEntry(resourceId, otherParams);
|
|
112
132
|
if (data) {
|
|
113
133
|
return {
|
|
114
134
|
data,
|
|
115
135
|
response: {
|
|
116
136
|
status: 200,
|
|
117
|
-
fromCache: true
|
|
118
|
-
}
|
|
119
|
-
}
|
|
137
|
+
fromCache: true,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
120
140
|
}
|
|
121
|
-
|
|
122
141
|
}
|
|
123
142
|
|
|
124
143
|
const result = callSync(`sx${serviceName}`, {
|
|
@@ -126,20 +145,18 @@ const fxGeneric = ({ serviceName, prop, subProp, method, params, options, cacher
|
|
|
126
145
|
prop,
|
|
127
146
|
method,
|
|
128
147
|
params: normalizeSerialization(params),
|
|
129
|
-
options: normalizeSerialization(options)
|
|
130
|
-
})
|
|
148
|
+
options: normalizeSerialization(options),
|
|
149
|
+
});
|
|
131
150
|
|
|
132
|
-
if (method ===
|
|
133
|
-
return register(resourceId, cacher, result, false, otherParams)
|
|
151
|
+
if (method === "get") {
|
|
152
|
+
return register(resourceId, cacher, result, false, otherParams);
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
if (resourceId) {
|
|
137
|
-
cacher.clear(resourceId)
|
|
156
|
+
cacher.clear(resourceId);
|
|
138
157
|
}
|
|
139
|
-
return result
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
158
|
+
return result;
|
|
159
|
+
};
|
|
143
160
|
|
|
144
161
|
/**
|
|
145
162
|
* sync a call to Drive api get
|
|
@@ -150,38 +167,43 @@ const fxGeneric = ({ serviceName, prop, subProp, method, params, options, cacher
|
|
|
150
167
|
* @param {object} p.params the params to add to the request
|
|
151
168
|
* @return {DriveResponse} from the drive api
|
|
152
169
|
*/
|
|
153
|
-
const fxDriveGet = ({
|
|
154
|
-
|
|
170
|
+
const fxDriveGet = ({
|
|
171
|
+
id,
|
|
172
|
+
params,
|
|
173
|
+
allow404 = false,
|
|
174
|
+
allowCache = true,
|
|
175
|
+
options,
|
|
176
|
+
}) => {
|
|
155
177
|
// fixup the fields param
|
|
156
178
|
// we'll fiddle with the scopes to populate cache
|
|
157
|
-
params.fields = mergeParamStrings(minFields, params.fields || "")
|
|
158
|
-
params.fileId = id
|
|
179
|
+
params.fields = mergeParamStrings(minFields, params.fields || "");
|
|
180
|
+
params.fileId = id;
|
|
159
181
|
|
|
160
182
|
// now we check if it's in cache and already has the necessary fields
|
|
161
183
|
// the cache will check the fields it already has against those requested
|
|
162
184
|
if (allowCache) {
|
|
163
|
-
const { cachedFile, good } = getFromFileCache(id, params.fields)
|
|
164
|
-
if (good)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
185
|
+
const { cachedFile, good } = getFromFileCache(id, params.fields);
|
|
186
|
+
if (good)
|
|
187
|
+
return {
|
|
188
|
+
data: cachedFile,
|
|
189
|
+
// fake a good sxresponse
|
|
190
|
+
response: {
|
|
191
|
+
status: 200,
|
|
192
|
+
fromCache: true,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
173
195
|
}
|
|
174
196
|
|
|
175
197
|
// so we have to hit the API
|
|
176
|
-
const result = callSync(
|
|
198
|
+
const result = callSync("sxDriveGet", {
|
|
177
199
|
id,
|
|
178
200
|
params: normalizeSerialization(params),
|
|
179
|
-
options: normalizeSerialization(options)
|
|
201
|
+
options: normalizeSerialization(options),
|
|
180
202
|
});
|
|
181
203
|
|
|
182
204
|
// check result and register in cache
|
|
183
|
-
return registerSx(result, allow404, params.fields)
|
|
184
|
-
}
|
|
205
|
+
return registerSx(result, allow404, params.fields);
|
|
206
|
+
};
|
|
185
207
|
|
|
186
208
|
/**
|
|
187
209
|
* zipper
|
|
@@ -190,26 +212,24 @@ const fxDriveGet = ({ id, params, allow404 = false, allowCache = true, options }
|
|
|
190
212
|
* @returns {FakeBlob} a combined zip file
|
|
191
213
|
*/
|
|
192
214
|
const fxZipper = ({ blobs }) => {
|
|
193
|
-
|
|
194
|
-
const dupCheck = new Set()
|
|
215
|
+
const dupCheck = new Set();
|
|
195
216
|
const blobsContent = blobs.map((f, i) => {
|
|
196
|
-
const ext = mime.getExtension(f.getContentType())
|
|
197
|
-
const name = f.getName() || `Untitled${i + 1}${ext ? "." + ext : ""}
|
|
217
|
+
const ext = mime.getExtension(f.getContentType());
|
|
218
|
+
const name = f.getName() || `Untitled${i + 1}${ext ? "." + ext : ""}`;
|
|
198
219
|
if (dupCheck.has(name)) {
|
|
199
|
-
throw new Error(`Duplicate filename ${name} not allowed in zip`)
|
|
220
|
+
throw new Error(`Duplicate filename ${name} not allowed in zip`);
|
|
200
221
|
}
|
|
201
|
-
dupCheck.add(name)
|
|
222
|
+
dupCheck.add(name);
|
|
202
223
|
return {
|
|
203
224
|
name,
|
|
204
|
-
bytes: f.getBytes()
|
|
205
|
-
}
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
return callSync('sxZipper', {
|
|
209
|
-
blobsContent
|
|
210
|
-
})
|
|
225
|
+
bytes: f.getBytes(),
|
|
226
|
+
};
|
|
227
|
+
});
|
|
211
228
|
|
|
212
|
-
|
|
229
|
+
return callSync("sxZipper", {
|
|
230
|
+
blobsContent,
|
|
231
|
+
});
|
|
232
|
+
};
|
|
213
233
|
|
|
214
234
|
/**
|
|
215
235
|
* Unzipper
|
|
@@ -218,16 +238,15 @@ const fxZipper = ({ blobs }) => {
|
|
|
218
238
|
* @returns {FakeBlob[]} each of the files unzipped
|
|
219
239
|
*/
|
|
220
240
|
const fxUnzipper = ({ blob }) => {
|
|
221
|
-
|
|
222
241
|
const blobContent = {
|
|
223
242
|
name: blob.getName(),
|
|
224
|
-
bytes: blob.getBytes()
|
|
225
|
-
}
|
|
243
|
+
bytes: blob.getBytes(),
|
|
244
|
+
};
|
|
226
245
|
|
|
227
|
-
return callSync(
|
|
228
|
-
blobContent
|
|
229
|
-
})
|
|
230
|
-
}
|
|
246
|
+
return callSync("sxUnzipper", {
|
|
247
|
+
blobContent,
|
|
248
|
+
});
|
|
249
|
+
};
|
|
231
250
|
|
|
232
251
|
/**
|
|
233
252
|
* initialize all the stuff at the beginning such as manifest content and settings
|
|
@@ -238,29 +257,28 @@ const fxUnzipper = ({ blob }) => {
|
|
|
238
257
|
* @param {string} p.settingsPath where to find the settings file
|
|
239
258
|
* @param {string} p.cachePath the cache files
|
|
240
259
|
* @param {string} p.propertiesPath the properties file location
|
|
241
|
-
* @return {object} the finalized vesions of all the above
|
|
260
|
+
* @return {object} the finalized vesions of all the above
|
|
242
261
|
*/
|
|
243
262
|
const fxInit = ({
|
|
244
263
|
manifestPath = manifestDefaultPath,
|
|
245
264
|
claspPath = claspDefaultPath,
|
|
246
265
|
settingsPath = settingsDefaultPath,
|
|
247
266
|
cachePath = cacheDefaultPath,
|
|
248
|
-
propertiesPath = propertiesDefaultPath
|
|
267
|
+
propertiesPath = propertiesDefaultPath,
|
|
249
268
|
} = {}) => {
|
|
250
|
-
|
|
251
269
|
// this is the path of the runing main process
|
|
252
|
-
const mainDir = path.dirname(process.argv[1])
|
|
270
|
+
const mainDir = path.dirname(process.argv[1]);
|
|
253
271
|
|
|
254
272
|
// because this is all run in a synced subprocess it's not an async result
|
|
255
|
-
const synced = callSync(
|
|
273
|
+
const synced = callSync("sxInit", {
|
|
256
274
|
claspPath,
|
|
257
275
|
settingsPath,
|
|
258
276
|
manifestPath,
|
|
259
277
|
mainDir,
|
|
260
278
|
cachePath,
|
|
261
279
|
propertiesPath,
|
|
262
|
-
fakeId: randomUUID()
|
|
263
|
-
})
|
|
280
|
+
fakeId: randomUUID(),
|
|
281
|
+
});
|
|
264
282
|
|
|
265
283
|
const {
|
|
266
284
|
scopes,
|
|
@@ -270,24 +288,20 @@ const fxInit = ({
|
|
|
270
288
|
accessToken,
|
|
271
289
|
settings,
|
|
272
290
|
manifest,
|
|
273
|
-
clasp
|
|
274
|
-
} = synced
|
|
291
|
+
clasp,
|
|
292
|
+
} = synced;
|
|
275
293
|
|
|
276
294
|
// set these values from the subprocess for the main project
|
|
277
|
-
Auth.setProjectId(projectId)
|
|
295
|
+
Auth.setProjectId(projectId);
|
|
278
296
|
//Auth.setAuth(scopes)
|
|
279
|
-
Auth.setTokenInfo(tokenInfo)
|
|
297
|
+
Auth.setTokenInfo(tokenInfo);
|
|
280
298
|
//Auth.setAdcPath(adcPath)
|
|
281
299
|
//Auth.setAccessToken(accessToken)
|
|
282
|
-
Auth.setSettings(settings)
|
|
283
|
-
Auth.setClasp(clasp)
|
|
284
|
-
Auth.setManifest(manifest)
|
|
285
|
-
return synced
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
300
|
+
Auth.setSettings(settings);
|
|
301
|
+
Auth.setClasp(clasp);
|
|
302
|
+
Auth.setManifest(manifest);
|
|
303
|
+
return synced;
|
|
304
|
+
};
|
|
291
305
|
|
|
292
306
|
/**
|
|
293
307
|
* because we're using a file backed cache we need to syncit
|
|
@@ -297,21 +311,17 @@ const fxInit = ({
|
|
|
297
311
|
* @returns {*}
|
|
298
312
|
*/
|
|
299
313
|
const fxStore = (storeArgs, method = "get", ...kvArgs) => {
|
|
300
|
-
|
|
301
|
-
return callSync('sxStore', {
|
|
314
|
+
return callSync("sxStore", {
|
|
302
315
|
method,
|
|
303
316
|
kvArgs,
|
|
304
|
-
storeArgs
|
|
305
|
-
})
|
|
306
|
-
}
|
|
307
|
-
|
|
317
|
+
storeArgs,
|
|
318
|
+
});
|
|
319
|
+
};
|
|
308
320
|
|
|
309
321
|
const fxRefreshToken = () => {
|
|
310
|
-
|
|
311
|
-
return callSync('sxRefreshToken');
|
|
322
|
+
return callSync("sxRefreshToken");
|
|
312
323
|
};
|
|
313
324
|
|
|
314
|
-
|
|
315
325
|
/**
|
|
316
326
|
* sync a call to Drive api to stream a download
|
|
317
327
|
* @param {object} p pargs
|
|
@@ -321,14 +331,10 @@ const fxRefreshToken = () => {
|
|
|
321
331
|
* @return {DriveResponse} from the drive api
|
|
322
332
|
*/
|
|
323
333
|
const fxDriveMedia = ({ id }) => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
id
|
|
334
|
+
return callSync("sxDriveMedia", {
|
|
335
|
+
id,
|
|
327
336
|
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
337
|
+
};
|
|
332
338
|
|
|
333
339
|
/**
|
|
334
340
|
* a sync version of fetching
|
|
@@ -338,15 +344,46 @@ const fxDriveMedia = ({ id }) => {
|
|
|
338
344
|
* @returns {reponse} urlfetch style reponse
|
|
339
345
|
*/
|
|
340
346
|
const fxFetch = (url, options, responseFields) => {
|
|
347
|
+
return callSync("sxFetch", url, options, responseFields);
|
|
348
|
+
};
|
|
341
349
|
|
|
342
|
-
|
|
343
|
-
|
|
350
|
+
const fxSheets = (args) =>
|
|
351
|
+
fxGeneric({
|
|
352
|
+
...args,
|
|
353
|
+
serviceName: "Sheets",
|
|
354
|
+
cacher: sheetsCacher,
|
|
355
|
+
idField: "spreadsheetId",
|
|
356
|
+
});
|
|
357
|
+
const fxSlides = (args) =>
|
|
358
|
+
fxGeneric({
|
|
359
|
+
...args,
|
|
360
|
+
serviceName: "Slides",
|
|
361
|
+
cacher: slidesCacher,
|
|
362
|
+
idField: "presentationId",
|
|
363
|
+
});
|
|
364
|
+
const fxDocs = (args) =>
|
|
365
|
+
fxGeneric({
|
|
366
|
+
...args,
|
|
367
|
+
serviceName: "Docs",
|
|
368
|
+
cacher: docsCacher,
|
|
369
|
+
idField: "documentId",
|
|
370
|
+
});
|
|
371
|
+
const fxForms = (args) =>
|
|
372
|
+
fxGeneric({
|
|
373
|
+
...args,
|
|
374
|
+
serviceName: "Forms",
|
|
375
|
+
cacher: formsCacher,
|
|
376
|
+
idField: "formId",
|
|
377
|
+
});
|
|
378
|
+
const fxGmail = (args) =>
|
|
379
|
+
fxGeneric({
|
|
380
|
+
...args,
|
|
381
|
+
serviceName: "Gmail",
|
|
382
|
+
cacher: gmailCacher,
|
|
383
|
+
idField: "id",
|
|
384
|
+
});
|
|
344
385
|
|
|
345
|
-
const
|
|
346
|
-
const fxSlides = (args) => fxGeneric({ ...args, serviceName: 'Slides', cacher: slidesCacher, idField: 'presentationId' });
|
|
347
|
-
const fxDocs = (args) => fxGeneric({ ...args, serviceName: 'Docs', cacher: docsCacher, idField: 'documentId' });
|
|
348
|
-
const fxForms = (args) => fxGeneric({ ...args, serviceName: 'Forms', cacher: formsCacher, idField: 'formId' });
|
|
349
|
-
const fxGmail = (args) => fxGeneric({ ...args, serviceName: 'Gmail', cacher: gmailCacher, idField: 'id' });
|
|
386
|
+
const fxGetImagesFromXlsx = (args) => callSync("sxGetImagesFromXlsx", args);
|
|
350
387
|
|
|
351
388
|
export const Syncit = {
|
|
352
389
|
fxFetch,
|
|
@@ -364,4 +401,5 @@ export const Syncit = {
|
|
|
364
401
|
fxDocs,
|
|
365
402
|
fxForms,
|
|
366
403
|
fxGmail,
|
|
367
|
-
|
|
404
|
+
fxGetImagesFromXlsx,
|
|
405
|
+
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
1
|
+
export * from "../sxdrive.js";
|
|
2
|
+
export * from "../sxsheets.js";
|
|
3
|
+
export * from "../sxdocs.js";
|
|
4
|
+
export * from "../sxslides.js";
|
|
5
|
+
export * from "../sxforms.js";
|
|
6
|
+
export * from "../sxfetch.js";
|
|
7
|
+
export * from "../sxstore.js";
|
|
8
|
+
export * from "../sxzip.js";
|
|
9
|
+
export * from "../sxauth.js";
|
|
10
|
+
export * from "../sxgmail.js";
|
|
11
|
+
export * from "../sxxlsx.js";
|