@mcpher/gas-fakes 1.0.21 → 1.0.23
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.RU.md +0 -1
- package/README.md +37 -4
- package/gasmess/bruce/pbx.js +1 -1
- package/gasmess/bruce/testlogging.js +7 -0
- package/gprompts/gas-inventory.js +71 -67
- package/package.json +44 -42
- package/src/index.js +5 -4
- package/src/services/logger/fakelogger.js +52 -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/togas.bash +1 -32
- package/setup.sh +0 -147
package/README.RU.md
CHANGED
|
@@ -328,7 +328,6 @@ const getParentsIterator = ({
|
|
|
328
328
|
- [oddities](oddities.md) - a collection of oddities uncovered during this project
|
|
329
329
|
- [gemini](gemini.md) - some reflections and experiences on using gemini to help code large projects
|
|
330
330
|
- [named colors](named-colors.md) - colors supported by Apps Script
|
|
331
|
-
- [setup env](setup-env.md) - ([credit Eric Shapiro] - additional info on contents of .env file
|
|
332
331
|
- [this file](README.md)
|
|
333
332
|
- [named colors](named-colors.md)
|
|
334
333
|
- [sandbox](sandbox.md)
|
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,47 @@ 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
|
+
#### A note on .env location
|
|
159
|
+
|
|
160
|
+
Optionally you can set the initial Logging environment in your .env file. By default it assumes you want to log to the console only.
|
|
161
|
+
If you want to set an initial LOG_DESTINATION using that .env file, you have to let gas-fakes know where to find it. Let's assume it's in the same folder as your main script.
|
|
162
|
+
```env
|
|
163
|
+
node --env-file=.env yourapp.js
|
|
164
|
+
```
|
|
165
|
+
Some developers prefer to use [dotenv](https://www.npmjs.com/package/dotenv) to set the path of the .env file
|
|
166
|
+
```javascript
|
|
167
|
+
import dotenv from 'dotenv'
|
|
168
|
+
dotenv.config({ path: '/custom/path/to/.env' })
|
|
169
|
+
```
|
|
170
|
+
Alternatively, instead of putting it in an env file, you can export it in your shell environment.
|
|
171
|
+
```sh
|
|
172
|
+
export LOG_DESTINATION="BOTH"
|
|
173
|
+
```
|
|
174
|
+
Finally, another approach is to set it dynamically at the beginning of your app.
|
|
175
|
+
```javascript
|
|
176
|
+
Logger.__logDestination="BOTH"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Do whichever one suits you best.
|
|
180
|
+
|
|
147
181
|
### Pushing files to GAS
|
|
148
182
|
|
|
149
183
|
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
|
|
@@ -173,7 +207,6 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
|
|
|
173
207
|
- [oddities](oddities.md) - a collection of oddities uncovered during this project
|
|
174
208
|
- [gemini](gemini.md) - some reflections and experiences on using gemini to help code large projects
|
|
175
209
|
- [named colors](named-colors.md) - colors supported by Apps Script
|
|
176
|
-
- [setup env](setup-env.md) - ([credit Eric Shapiro] - additional info on contents of .env file
|
|
177
210
|
- [this file](README.md)
|
|
178
211
|
- [named colors](named-colors.md)
|
|
179
212
|
- [sandbox](sandbox.md)
|
package/gasmess/bruce/pbx.js
CHANGED
|
@@ -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,56 +17,58 @@
|
|
|
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",
|
|
23
|
-
"unzipper": "^0.12.3"
|
|
24
|
+
"unzipper": "^0.12.3",
|
|
25
|
+
"yoctocolors": "^2.1.2"
|
|
24
26
|
},
|
|
25
27
|
"type": "module",
|
|
26
28
|
"scripts": {
|
|
27
|
-
"test": "cp mainlocal.js main.js && node
|
|
28
|
-
"testdrive": "cp mainlocal.js main.js && node
|
|
29
|
-
"testsheetsdatavalidations": "cp mainlocal.js main.js && node
|
|
30
|
-
"testsheetspermissions": "cp mainlocal.js main.js && node
|
|
31
|
-
"testsheetsvalues": "cp mainlocal.js main.js && node
|
|
32
|
-
"testsheets": "cp mainlocal.js main.js && node
|
|
33
|
-
"testfetch": "cp mainlocal.js main.js && node
|
|
34
|
-
"testsession": "cp mainlocal.js main.js && node
|
|
35
|
-
"testutilities": "cp mainlocal.js main.js && node
|
|
36
|
-
"teststores": "cp mainlocal.js main.js && node
|
|
37
|
-
"testscriptapp": "cp mainlocal.js main.js &&
|
|
38
|
-
"testfiddler": "cp mainlocal.js main.js &&
|
|
39
|
-
"testenums": "cp mainlocal.js main.js &&
|
|
40
|
-
"testsheetssets": "cp mainlocal.js main.js &&
|
|
41
|
-
"testsheetsvui": "cp mainlocal.js main.js &&
|
|
42
|
-
"testsheetsdeveloper": "cp mainlocal.js main.js &&
|
|
43
|
-
"testsheetsexotics": "cp mainlocal.js main.js &&
|
|
44
|
-
"testsheetsdata": "cp mainlocal.js main.js &&
|
|
45
|
-
"testdocsadv": "cp mainlocal.js main.js &&
|
|
46
|
-
"testslidesadv": "cp mainlocal.js main.js &&
|
|
47
|
-
"testform": "cp mainlocal.js main.js &&
|
|
48
|
-
"testformsadv": "cp mainlocal.js main.js &&
|
|
49
|
-
"testdocs": "cp mainlocal.js main.js &&
|
|
50
|
-
"testslides": "cp mainlocal.js main.js &&
|
|
51
|
-
"testdocsnext": "cp mainlocal.js main.js &&
|
|
52
|
-
"testsheetstext": "cp mainlocal.js main.js &&
|
|
53
|
-
"testsheetsrange": "cp mainlocal.js main.js &&
|
|
54
|
-
"testdocslistitems": "cp mainlocal.js main.js &&
|
|
55
|
-
"testdocsall": "cp mainlocal.js main.js &&
|
|
56
|
-
"testdocsheaders": "cp mainlocal.js main.js &&
|
|
57
|
-
"testdocsfooters": "cp mainlocal.js main.js &&
|
|
58
|
-
"testdocsfootnotes": "cp mainlocal.js main.js &&
|
|
59
|
-
"testdocsimages": "cp mainlocal.js main.js &&
|
|
60
|
-
"testdocsstyles": "cp mainlocal.js main.js &&
|
|
61
|
-
"testsandbox": "cp mainlocal.js main.js &&
|
|
62
|
-
"testgmail": "cp mainlocal.js main.js &&
|
|
63
|
-
"testlogger": "cp mainlocal.js main.js &&
|
|
64
|
-
"pub": "cp mainlocal.js main.js
|
|
29
|
+
"test": "cp mainlocal.js main.js && node ./test/test.js",
|
|
30
|
+
"testdrive": "cp mainlocal.js main.js && node ./test/testdrive.js execute",
|
|
31
|
+
"testsheetsdatavalidations": "cp mainlocal.js main.js && node ./test/testsheetsdatavalidations.js execute",
|
|
32
|
+
"testsheetspermissions": "cp mainlocal.js main.js && node ./test/testsheetspermissions.js execute",
|
|
33
|
+
"testsheetsvalues": "cp mainlocal.js main.js && node ./test/testsheetsvalues.js execute",
|
|
34
|
+
"testsheets": "cp mainlocal.js main.js && node ./test/testsheets.js execute",
|
|
35
|
+
"testfetch": "cp mainlocal.js main.js && node ./test/testfetch.js execute",
|
|
36
|
+
"testsession": "cp mainlocal.js main.js && node ./test/testsession.js execute",
|
|
37
|
+
"testutilities": "cp mainlocal.js main.js && node ./test/testutilities.js execute",
|
|
38
|
+
"teststores": "cp mainlocal.js main.js && node ./test/teststores.js execute",
|
|
39
|
+
"testscriptapp": "cp mainlocal.js main.js && node ./test/testscriptapp.js execute",
|
|
40
|
+
"testfiddler": "cp mainlocal.js main.js && node ./test/testfiddler.js execute",
|
|
41
|
+
"testenums": "cp mainlocal.js main.js && node ./test/testenums.js execute",
|
|
42
|
+
"testsheetssets": "cp mainlocal.js main.js && node ./test/testsheetssets.js execute",
|
|
43
|
+
"testsheetsvui": "cp mainlocal.js main.js && node ./test/testsheetsvui.js execute",
|
|
44
|
+
"testsheetsdeveloper": "cp mainlocal.js main.js && node ./test/testsheetsdeveloper.js execute",
|
|
45
|
+
"testsheetsexotics": "cp mainlocal.js main.js && node ./test/testsheetsexotics.js execute",
|
|
46
|
+
"testsheetsdata": "cp mainlocal.js main.js && node ./test/testsheetsdata.js execute",
|
|
47
|
+
"testdocsadv": "cp mainlocal.js main.js && node ./test/testdocsadv.js execute",
|
|
48
|
+
"testslidesadv": "cp mainlocal.js main.js && node ./test/testslidesadv.js execute",
|
|
49
|
+
"testform": "cp mainlocal.js main.js && node ./test/testform.js execute",
|
|
50
|
+
"testformsadv": "cp mainlocal.js main.js && node ./test/testformsadv.js execute",
|
|
51
|
+
"testdocs": "cp mainlocal.js main.js && node ./test/testdocs.js execute",
|
|
52
|
+
"testslides": "cp mainlocal.js main.js && node ./test/testslides.js execute",
|
|
53
|
+
"testdocsnext": "cp mainlocal.js main.js && node ./test/testdocsnext.js execute",
|
|
54
|
+
"testsheetstext": "cp mainlocal.js main.js && node ./test/testsheetstext.js execute",
|
|
55
|
+
"testsheetsrange": "cp mainlocal.js main.js && node ./test/testsheetsrange.js execute",
|
|
56
|
+
"testdocslistitems": "cp mainlocal.js main.js && node ./test/testdocslistitems.js execute",
|
|
57
|
+
"testdocsall": "cp mainlocal.js main.js && node ./test/testdocsall.js",
|
|
58
|
+
"testdocsheaders": "cp mainlocal.js main.js && node ./test/testdocsheaders.js execute",
|
|
59
|
+
"testdocsfooters": "cp mainlocal.js main.js && node ./test/testdocsfooters.js execute",
|
|
60
|
+
"testdocsfootnotes": "cp mainlocal.js main.js && node ./test/testdocsfootnotes.js execute",
|
|
61
|
+
"testdocsimages": "cp mainlocal.js main.js && node ./test/testdocsimages.js execute",
|
|
62
|
+
"testdocsstyles": "cp mainlocal.js main.js && node ./test/testdocsstyles.js execute",
|
|
63
|
+
"testsandbox": "cp mainlocal.js main.js && node ./test/testsandbox.js execute",
|
|
64
|
+
"testgmail": "cp mainlocal.js main.js && node ./test/testgmail.js execute",
|
|
65
|
+
"testlogger": "cp mainlocal.js main.js && node ./test/testlogger.js execute",
|
|
66
|
+
"pub": "cp mainlocal.js main.js && npm publish --access public"
|
|
65
67
|
},
|
|
66
68
|
"name": "@mcpher/gas-fakes",
|
|
67
|
-
"version": "1.0.
|
|
69
|
+
"version": "1.0.23",
|
|
68
70
|
"license": "MIT",
|
|
69
|
-
"main": "main.js",
|
|
71
|
+
"main": "main.js && node ",
|
|
70
72
|
"description": "A proof of concept implementation of Apps Script Environment on Node",
|
|
71
73
|
"repository": "github:brucemcpherson/gas-fakes",
|
|
72
74
|
"homepage": "https://ramblings.mcpher.com/a-proof-of-concept-implementation-of-apps-script-environment-on-node/"
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import colors from 'yoctocolors'
|
|
1
2
|
import './services/scriptapp/app.js'
|
|
3
|
+
|
|
4
|
+
import {default as pf } from '../package.json' with { type: 'json' };
|
|
5
|
+
console.log (`You are using ${colors.blueBright(pf.name)} package version ${colors.greenBright(pf.version)}`)
|
|
6
|
+
ScriptApp.__gasFakesVersion = pf.version
|
|
2
7
|
import './services/driveapp/app.js'
|
|
3
8
|
import './services/logger/app.js'
|
|
4
9
|
import './services/urlfetchapp/app.js'
|
|
@@ -16,7 +21,3 @@ import './services/documentapp/app.js'
|
|
|
16
21
|
import './services/advforms/app.js'
|
|
17
22
|
import './services/formapp/app.js'
|
|
18
23
|
import './services/slidesapp/app.js'
|
|
19
|
-
|
|
20
|
-
// force initilialization of scriptApp - which is always needed
|
|
21
|
-
// scriptapp will always be fake here
|
|
22
|
-
ScriptApp.isFake
|
|
@@ -23,6 +23,11 @@ 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
|
+
if (['CLOUD','BOTH'].includes(this.__destination)) {
|
|
29
|
+
writeToCloudOrConsole("...Initializing cloud logging", this)
|
|
30
|
+
}
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/**
|
|
@@ -58,6 +63,7 @@ export class FakeLogger {
|
|
|
58
63
|
const message = format(formatOrData, ...values);
|
|
59
64
|
this.__log.push(message);
|
|
60
65
|
|
|
66
|
+
|
|
61
67
|
// Pass the destination and instance context to the writer function.
|
|
62
68
|
writeToCloudOrConsole(message, this);
|
|
63
69
|
|
|
@@ -81,8 +87,16 @@ export class FakeLogger {
|
|
|
81
87
|
if (['CONSOLE', 'CLOUD', 'BOTH', 'NONE'].includes(destination.toUpperCase())) {
|
|
82
88
|
this.__destination = destination.toUpperCase();
|
|
83
89
|
}
|
|
90
|
+
// we need to send a logger message to initialize and test
|
|
91
|
+
this.log (`...setting destination to ${this.__destination}`)
|
|
84
92
|
return this;
|
|
85
93
|
}
|
|
94
|
+
|
|
95
|
+
// to allow code compatibility without error on apps script, we should use the setter since it'll be undefined on Apps live Script
|
|
96
|
+
set __logDestination (destination) {
|
|
97
|
+
return this.__setLogDestination(destination);
|
|
98
|
+
}
|
|
99
|
+
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
/**
|
|
@@ -96,21 +110,23 @@ export const newFakeLogger = (...args) => Proxies.guard(new FakeLogger(...args))
|
|
|
96
110
|
* @private
|
|
97
111
|
* @param {FakeLogger} loggerInstance The logger instance, to access its destination.
|
|
98
112
|
*/
|
|
99
|
-
const writeToCloudOrConsole =
|
|
113
|
+
const writeToCloudOrConsole = (message, loggerInstance) => {
|
|
100
114
|
const logDestination = loggerInstance.__destination;
|
|
101
115
|
const useConsoleLogging = logDestination === 'CONSOLE' || logDestination === 'BOTH';
|
|
102
116
|
const useCloudLogging = logDestination === 'CLOUD' || logDestination === 'BOTH';
|
|
117
|
+
const logName = 'gas-fakes/console_logs';
|
|
118
|
+
const projectId = ScriptApp.__projectId;
|
|
119
|
+
const scriptId = ScriptApp.getScriptId();
|
|
120
|
+
const userId = ScriptApp.__userId;
|
|
103
121
|
|
|
104
122
|
// Lazy-initialize the cloud logger if needed.
|
|
105
123
|
const initializeCloudLogging = () => {
|
|
106
124
|
if (cloudLog) return; // Already initialized.
|
|
107
125
|
try {
|
|
108
|
-
const projectId = ScriptApp.__projectId;
|
|
109
126
|
if (!projectId) {
|
|
110
127
|
throw new Error('Could not determine Google Cloud Project ID for logging.');
|
|
111
128
|
}
|
|
112
129
|
const logging = new Logging({ projectId });
|
|
113
|
-
const logName = 'gas-fakes/console_logs';
|
|
114
130
|
cloudLog = logging.log(logName);
|
|
115
131
|
console.info(`gas-fakes: Cloud Logging is enabled, writing to log "${logName}".`);
|
|
116
132
|
} catch (err) {
|
|
@@ -145,8 +161,8 @@ const writeToCloudOrConsole = (message, loggerInstance) => {
|
|
|
145
161
|
// not running on a specific GCP compute service.
|
|
146
162
|
resource: { type: 'global' },
|
|
147
163
|
labels: {
|
|
148
|
-
'gas-fakes-scriptId':
|
|
149
|
-
'gas-fakes-userId':
|
|
164
|
+
'gas-fakes-scriptId': scriptId,
|
|
165
|
+
'gas-fakes-userId': userId,
|
|
150
166
|
},
|
|
151
167
|
jsonPayload: { message: message },
|
|
152
168
|
severity: 'INFO',
|
|
@@ -156,7 +172,36 @@ const writeToCloudOrConsole = (message, loggerInstance) => {
|
|
|
156
172
|
// his allows us to catch errors, such as a disabled API or permission issues.
|
|
157
173
|
// however we are firing and forgetting so an error might appear later on in execution
|
|
158
174
|
if (cloudLog) {
|
|
159
|
-
|
|
175
|
+
// filter on user/script/priject and run times
|
|
176
|
+
// we'll extend the end date filter to a little more than now
|
|
177
|
+
const aLittleMore = 7 * 1000
|
|
178
|
+
const endDate = new Date(new Date().getTime() + aLittleMore)
|
|
179
|
+
const startDate = loggerInstance.__startedLoggingAt
|
|
180
|
+
|
|
181
|
+
const base = `https://console.cloud.google.com/logs/query;`
|
|
182
|
+
|
|
183
|
+
// B. Build the RAW LQL Filter String (NO "query=" prefix here)
|
|
184
|
+
const lqlParts = [
|
|
185
|
+
// LogName filter
|
|
186
|
+
`logName="projects/${projectId}/logs/${logName.replace('/', '%2F')}"`,
|
|
187
|
+
// Labels filter
|
|
188
|
+
...Object.keys(metadata.labels).map(k => `jsonPayload.labels.${k}="${metadata.labels[k]}"`),
|
|
189
|
+
// Date filter (recommended for query stability)
|
|
190
|
+
`timestamp>="${startDate.toISOString()}"`,
|
|
191
|
+
`timestamp<="${endDate.toISOString()}"`
|
|
192
|
+
];
|
|
193
|
+
const fullLQLQuery = lqlParts.join(' AND ');
|
|
194
|
+
|
|
195
|
+
// C. Encode ONLY the filter content, then prepend the literal 'query='
|
|
196
|
+
const encodedQueryParam = `query=${encodeURIComponent(fullLQLQuery)}`;
|
|
197
|
+
|
|
198
|
+
// D. Create the timeRange parameter (correct casing)
|
|
199
|
+
const timeRangeParam = `;timeRange=${startDate.getTime()}/${endDate.getTime()}`;
|
|
200
|
+
|
|
201
|
+
// E. Assemble the final URL
|
|
202
|
+
loggerInstance.__cloudLogLink = base + encodedQueryParam + `?project=${projectId}`;
|
|
203
|
+
// fire and forget
|
|
204
|
+
cloudLog.write(cloudLog.entry(metadata)).catch(err => console.error('gas-fakes: Failed to write to Cloud Logging.', err.message))
|
|
160
205
|
}
|
|
161
206
|
}
|
|
162
|
-
};
|
|
207
|
+
};
|
|
@@ -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
|
+
};
|