@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 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 using the `Logger.__setLogDestination()` method. This is especially useful for testing or for applications that need to change their logging behavior dynamically.
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.__setLogDestination('CONSOLE');
140
+ Logger.__logDestination='CONSOLE';
141
141
  Logger.log('This now only goes to the console');
142
142
 
143
143
  // Disable logging completely
144
- Logger.__setLogDestination('NONE');
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
- while (queue.length > 0) {
22
- const url = queue.shift();
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
- $('a').each((i, el) => {
35
- const href = $(el).attr('href');
36
- if (href && href.startsWith('/apps-script/reference')) {
37
- const absoluteUrl = new URL(href, url).href;
38
- const parentUrl = absoluteUrl.replace (/#.*/, '')
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
- // Extract classes and methods
58
- $('h2').each((i, el) => {
59
- const className = $(el).text().trim();
60
- if (className) {
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
- if (methodName) {
75
- inventory[serviceName].classes[className].methods[methodName] = {
76
- link: methodLink ? new URL(methodLink, url).href : '',
77
- returns: {
78
- type: returnType,
79
- link: '' // to be extracted later
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
- await fs.writeFile(outputFile, JSON.stringify(inventory, null, 2));
89
- console.log(`Inventory saved to ${outputFile}`);
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
- scrape().catch(console.error);
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.21",
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 = (message, loggerInstance) => {
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': ScriptApp.getScriptId(),
149
- 'gas-fakes-userId': ScriptApp.__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
- cloudLog.write(cloudLog.entry(metadata)).catch(err=> console.error('gas-fakes: Failed to write to Cloud Logging.', err.message))
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
+ };
@@ -1,49 +1,54 @@
1
1
  // this one is locally patched for now
2
2
  // import makeSynchronous from 'make-synchronous';
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 { improveFileCache, checkResponse, getFromFileCache } from "./filecache.js"
10
- import { checkResponseCacher } from './fetchcacher.js';
11
- import { docsCacher } from "./docscacher.js"
12
- import { gmailCacher } from './gmailcacher.js';
13
- import { formsCacher } from './formscacher.js';
14
- import { slidesCacher } from './slidescacher.js';
15
- import { sheetsCacher } from './sheetscacher.js';
16
- import is from '@sindresorhus/is';
17
- import { callSync } from './workersync/synchronizer.js';
18
-
19
-
20
- const manifestDefaultPath = './appsscript.json'
21
- const claspDefaultPath = "./.clasp.json"
22
- const settingsDefaultPath = "./gasfakes.json"
23
- const propertiesDefaultPath = "/tmp/gas-fakes/properties"
24
- const cacheDefaultPath = "/tmp/gas-fakes/cache"
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) => is.nullOrUndefined(ob) || !is.object(ob) ? ob : JSON.parse(JSON.stringify(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 = ({ file = {}, blob, fields = "", method = "create", fileId, params = {} }) => {
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('sxStreamUpMedia', {
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('sxDrive', {
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 = ({ serviceName, prop, subProp, method, params, options, cacher, idField }) => {
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 === 'get') {
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 === 'get') {
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 = ({ id, params, allow404 = false, allowCache = true, options }) => {
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) return {
165
-
166
- data: cachedFile,
167
- // fake a good sxresponse
168
- response: {
169
- status: 200,
170
- fromCache: true
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('sxDriveGet', {
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('sxUnzipper', {
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('sxInit', {
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
- return callSync('sxDriveMedia', {
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
- return callSync('sxFetch', url, options, responseFields);
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 fxSheets = (args) => fxGeneric({ ...args, serviceName: 'Sheets', cacher: sheetsCacher, idField: 'spreadsheetId' });
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 '../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';
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";