@mcpher/gas-fakes 1.0.14 → 1.0.15
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 +20 -1
- package/README.md +2 -1
- package/gasmess/bruce/d.js +4 -0
- package/gasmess/bruce/d1.js +4 -0
- package/gasmess/bruce/dreport.js +81 -0
- package/gasmess/bruce/pbx.js +33 -45
- package/gasmess/tanaike/for_simple_test.js +7 -1
- package/gasmess/tanaike/sample4.js +59 -0
- package/ghissues/sandbox-issue.sh +31 -0
- package/package.json +10 -6
- package/src/services/advdocs/docapis.js +10 -6
- package/src/services/advdocs/fakeadvdocs.js +1 -3
- package/src/services/advdocs/fakeadvdocuments.js +18 -3
- package/src/services/advdrive/drapis.js +10 -7
- package/src/services/advdrive/fakeadvdrive.js +19 -2
- package/src/services/advdrive/fakeadvdrivefiles.js +8 -5
- package/src/services/advdrive/fakeadvdrivepermissions.js +4 -4
- package/src/services/advsheets/fakeadvsheets.js +15 -5
- package/src/services/advsheets/fakeadvsheetsdevelopermetadata.js +3 -2
- package/src/services/advsheets/fakeadvsheetsspreadsheets.js +5 -6
- package/src/services/advsheets/fakeadvsheetsvalues.js +3 -2
- package/src/services/advsheets/shapis.js +18 -8
- package/src/services/advslides/fakeadvslides.js +20 -2
- package/src/services/advslides/slapis.js +10 -6
- package/src/services/documentapp/appenderhelpers.js +78 -17
- package/src/services/documentapp/elementblasters.js +61 -13
- package/src/services/documentapp/elementhelpers.js +38 -1
- package/src/services/documentapp/elementoptions.js +95 -46
- package/src/services/documentapp/elements.js +3 -0
- package/src/services/documentapp/fakebody.js +21 -7
- package/src/services/documentapp/fakebookmark.js +96 -0
- package/src/services/documentapp/fakecontainerelement.js +17 -12
- package/src/services/documentapp/fakedocument.js +111 -5
- package/src/services/documentapp/fakedocumentapp.js +3 -2
- package/src/services/documentapp/fakedocumenttab.js +2 -2
- package/src/services/documentapp/fakeelement.js +13 -8
- package/src/services/documentapp/fakefootersection.js +44 -0
- package/src/services/documentapp/fakeheadersection.js +44 -0
- package/src/services/documentapp/fakehorizontalrule.js +2 -2
- package/src/services/documentapp/fakelistitem.js +188 -0
- package/src/services/documentapp/fakepagebreak.js +2 -2
- package/src/services/documentapp/fakeparagraph.js +4 -4
- package/src/services/documentapp/fakeposition.js +84 -0
- package/src/services/documentapp/fakesectionelement.js +156 -0
- package/src/services/documentapp/faketable.js +28 -2
- package/src/services/documentapp/faketablecell.js +2 -2
- package/src/services/documentapp/faketablerow.js +2 -2
- package/src/services/documentapp/faketext.js +2 -2
- package/src/services/documentapp/fakeui.js +27 -0
- package/src/services/documentapp/nrhelpers.js +54 -19
- package/src/services/documentapp/shadowdocument.js +120 -40
- package/src/services/driveapp/driveiterators.js +8 -2
- package/src/services/driveapp/fakefolderapp.js +1 -0
- package/src/services/scriptapp/app.js +6 -6
- package/src/services/scriptapp/behavior.js +93 -0
- package/src/services/spreadsheetapp/fakenamedrange.js +120 -0
- package/src/services/spreadsheetapp/fakesheet.js +40 -2
- package/src/services/spreadsheetapp/fakespreadsheet.js +86 -3
- package/src/services/urlfetchapp/app.js +0 -1
- package/src/support/auth.js +14 -20
- package/src/support/backoff.js +52 -0
- package/src/support/sxauth.js +9 -11
- package/src/support/sxdocs.js +7 -9
- package/src/support/sxdrive.js +10 -14
- package/src/support/sxfetch.js +4 -3
- package/src/support/sxsheets.js +8 -8
- package/src/support/sxslides.js +8 -8
- package/src/support/sxstore.js +1 -1
- package/src/support/sxzip.js +1 -1
- package/src/support/syncit.js +5 -5
- package/src/support/utils.js +21 -0
- package/src/support/workersync/worker.js +5 -3
- package/shadowhelpers.js +0 -0
- package/src/services/advdrive/fakeadvvalues.js +0 -0
- package/src/services/documentapp/shadow.js +0 -121
package/README.RU.md
CHANGED
|
@@ -312,4 +312,23 @@ const getParentsIterator = ({
|
|
|
312
312
|
|
|
313
313
|
## Помощь
|
|
314
314
|
|
|
315
|
-
Как я уже упоминал ранее, чтобы развивать это дальше, мне понадобится много помощи для расширения поддерживаемых методов и сервисов - поэтому, если вы считаете, что это будет полезно для вас, и хотите сотрудничать, пожалуйста, свяжитесь со мной по [bruce@mcpher.com](mailto:bruce@mcpher.com) и мы поговорим.
|
|
315
|
+
Как я уже упоминал ранее, чтобы развивать это дальше, мне понадобится много помощи для расширения поддерживаемых методов и сервисов - поэтому, если вы считаете, что это будет полезно для вас, и хотите сотрудничать, пожалуйста, свяжитесь со мной по [bruce@mcpher.com](mailto:bruce@mcpher.com) и мы поговорим.
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
## Translations and writeups
|
|
319
|
+
|
|
320
|
+
- [initial idea and thoughts](https://ramblings.mcpher.com/a-proof-of-concept-implementation-of-apps-script-environment-on-node/)
|
|
321
|
+
- [Inside the volatile world of a Google Document](https://ramblings.mcpher.com/inside-the-volatile-world-of-a-google-document/
|
|
322
|
+
- [Apps Script Services on Node – using apps script libraries](https://ramblings.mcpher.com/apps-script-services-on-node-using-apps-script-libraries/)
|
|
323
|
+
- [Apps Script environment on Node – more services](https://ramblings.mcpher.com/apps-script-environment-on-node-more-services/)
|
|
324
|
+
- [Turning async into synch on Node using workers](https://ramblings.mcpher.com/turning-async-into-synch-on-node-using-workers/)
|
|
325
|
+
- [All about Apps Script Enums and how to fake them](https://ramblings.mcpher.com/all-about-apps-script-enums-and-how-to-fake-them/)
|
|
326
|
+
- [Russian version](README.RU.md) ([credit Alex Ivanov](https://github.com/oshliaer)) - needs updating
|
|
327
|
+
- [colaborators](collaborators.md) - additional information for collaborators
|
|
328
|
+
- [oddities](oddities.md) - a collection of oddities uncovered during this project
|
|
329
|
+
- [gemini](gemini.md) - some reflections and experiences on using gemini to help code large projects
|
|
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
|
+
- [this file](README.md)
|
|
333
|
+
- [named colors](named-colors.md)
|
|
334
|
+
- [sandbox](sandbox.md)
|
package/README.md
CHANGED
|
@@ -143,4 +143,5 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
|
|
|
143
143
|
- [named colors](named-colors.md) - colors supported by Apps Script
|
|
144
144
|
- [setup env](setup-env.md) - ([credit Eric Shapiro] - additional info on contents of .env file
|
|
145
145
|
- [this file](README.md)
|
|
146
|
-
- [named colors](named-colors.md)
|
|
146
|
+
- [named colors](named-colors.md)
|
|
147
|
+
- [sandbox](sandbox.md)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import is from '@sindresorhus/is';
|
|
2
|
+
export const whichType = (element) => {
|
|
3
|
+
const ts = ["paragraph", "pageBreak", "textRun", "table", "tableRows", "tableCells", "content"]
|
|
4
|
+
const [t] = ts.filter(f => Reflect.has(element, f))
|
|
5
|
+
return t
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const report = (doc, what) => {
|
|
9
|
+
let newdoc = doc
|
|
10
|
+
let body = doc.body
|
|
11
|
+
let tabs = doc.tabs
|
|
12
|
+
// handle case where tabs is handling legacy doc.
|
|
13
|
+
if (!body) {
|
|
14
|
+
if (!tabs) {
|
|
15
|
+
console.log('missing tabs')
|
|
16
|
+
console.log(JSON.stringify(doc))
|
|
17
|
+
} else {
|
|
18
|
+
console.log('getting body from tabs')
|
|
19
|
+
newdoc = tabs[0].documentTab
|
|
20
|
+
body = newdoc.body
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// drop the section break
|
|
25
|
+
const children = body.content.slice(1)
|
|
26
|
+
what += ` -children:${children.length}`
|
|
27
|
+
console.log(what)
|
|
28
|
+
let text = ' '
|
|
29
|
+
console.log(stringCircular(children))
|
|
30
|
+
const childProps = ["elements", "tableRows", "tableCells", "content", "listItem"]
|
|
31
|
+
const typer = (child, text, spaces = " ") => {
|
|
32
|
+
const type = whichType(child)
|
|
33
|
+
if (type) {
|
|
34
|
+
text += `\n${spaces}-${type} ${child.startIndex}:${child.endIndex}`
|
|
35
|
+
if (type === 'textRun') {
|
|
36
|
+
text += ` ${JSON.stringify(child[type].content)}`
|
|
37
|
+
}
|
|
38
|
+
const key = Reflect.ownKeys(child[type]).find(f => childProps.includes(f))
|
|
39
|
+
let arr = key && child[type][key]
|
|
40
|
+
if (!arr && is.array(child[type])) arr = child[type]
|
|
41
|
+
|
|
42
|
+
if (is.array(arr)) {
|
|
43
|
+
//text += spaces
|
|
44
|
+
arr.forEach(f => text = typer(f, text, spaces + " "))
|
|
45
|
+
//text += ''
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return text
|
|
49
|
+
}
|
|
50
|
+
return children.map(f => typer(f, text)).join("\n")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
export const scl = (doc) => {
|
|
55
|
+
if (!DocumentApp.isFake) {
|
|
56
|
+
const id = doc.getId()
|
|
57
|
+
doc.saveAndClose()
|
|
58
|
+
doc = DocumentApp.openById(id)
|
|
59
|
+
}
|
|
60
|
+
return doc
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The custom replacer function
|
|
64
|
+
const getCircularReplacer = () => {
|
|
65
|
+
const seen = new WeakSet(); // Use WeakSet to avoid memory leaks
|
|
66
|
+
return (_, value) => {
|
|
67
|
+
// If the value is an object and not null
|
|
68
|
+
if (typeof value === "object" && value !== null) {
|
|
69
|
+
// If we have already seen this object, it's a circular reference
|
|
70
|
+
if (seen.has(value)) {
|
|
71
|
+
return "[Circular]"; // Replace it with a placeholder
|
|
72
|
+
}
|
|
73
|
+
// If it's a new object, add it to our cache
|
|
74
|
+
seen.add(value);
|
|
75
|
+
}
|
|
76
|
+
// Return the value to be serialized
|
|
77
|
+
return value;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const stringCircular = (ob) => JSON.stringify(ob, getCircularReplacer());
|
package/gasmess/bruce/pbx.js
CHANGED
|
@@ -1,54 +1,26 @@
|
|
|
1
1
|
import '../../main.js';
|
|
2
2
|
import { moveToTempFolder, deleteTempFile } from '../tempfolder.js';
|
|
3
|
-
import
|
|
3
|
+
import { report, scl } from './dreport.js';
|
|
4
|
+
|
|
4
5
|
const suffix = "-bruce"
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
const ts = ["paragraph", "pageBreak", "textRun","table","tableRows","tableCells","content"]
|
|
8
|
-
const [t] = ts.filter(f => Reflect.has(element, f))
|
|
9
|
-
//if (!t) console.log('skipping element', element)
|
|
10
|
-
return t
|
|
11
|
-
}
|
|
7
|
+
const tabsa = () => {
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
console.log(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (type) {
|
|
25
|
-
text += `\n${spaces}-${type} ${child.startIndex}:${child.endIndex}`
|
|
26
|
-
if (type === 'textRun') {
|
|
27
|
-
text += ` ${JSON.stringify(child[type].content)}`
|
|
28
|
-
}
|
|
29
|
-
const key = Reflect.ownKeys(child[type]).find (f=>childProps.includes(f))
|
|
30
|
-
let arr = key && child[type][key]
|
|
31
|
-
if (!arr && is.array (child[type])) arr = child[type]
|
|
32
|
-
|
|
33
|
-
if (is.array(arr)) {
|
|
34
|
-
//text += spaces
|
|
35
|
-
arr.forEach(f => text = typer(f, text, spaces + " "))
|
|
36
|
-
//text += ''
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return text
|
|
40
|
-
}
|
|
41
|
-
return children.map(f => typer(f, text)).join("\n")
|
|
42
|
-
}
|
|
43
|
-
const scl = (doc) => {
|
|
44
|
-
if (!DocumentApp.isFake) {
|
|
45
|
-
const id = doc.getId()
|
|
46
|
-
doc.saveAndClose()
|
|
47
|
-
doc = DocumentApp.openById(id)
|
|
48
|
-
}
|
|
49
|
-
return doc
|
|
9
|
+
let doc = DocumentApp.create("abc")
|
|
10
|
+
const id = doc.getId()
|
|
11
|
+
moveToTempFolder(id, suffix)
|
|
12
|
+
|
|
13
|
+
let body = doc.getBody()
|
|
14
|
+
console.log (body)
|
|
15
|
+
doc = scl(doc)
|
|
16
|
+
console.log(report(Docs.Documents.get(id, {includeTabsContent: true}), `\n1.empty document`))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
deleteTempFile(id)
|
|
50
20
|
}
|
|
51
21
|
|
|
22
|
+
//tabsa()
|
|
23
|
+
|
|
52
24
|
// this is testing the new simplified method
|
|
53
25
|
const pbnew = () => {
|
|
54
26
|
let doc = DocumentApp.create("abc")
|
|
@@ -56,6 +28,16 @@ const pbnew = () => {
|
|
|
56
28
|
moveToTempFolder(id, suffix)
|
|
57
29
|
let body
|
|
58
30
|
|
|
31
|
+
body = doc.getBody()
|
|
32
|
+
body.appendListItem('l0a append')
|
|
33
|
+
doc = scl(doc)
|
|
34
|
+
console.log(report(Docs.Documents.get(id), `\nl0a.appended list item l0a`))
|
|
35
|
+
|
|
36
|
+
body = doc.getBody()
|
|
37
|
+
body.appendListItem('l01b append')
|
|
38
|
+
doc = scl(doc)
|
|
39
|
+
console.log(report(Docs.Documents.get(id), `\nl0a.appended list item l0b`))
|
|
40
|
+
|
|
59
41
|
body = doc.getBody()
|
|
60
42
|
body.appendTable([['']])
|
|
61
43
|
doc = scl(doc)
|
|
@@ -66,6 +48,12 @@ const pbnew = () => {
|
|
|
66
48
|
doc = scl(doc)
|
|
67
49
|
console.log(report(Docs.Documents.get(id), `\n1.appended para`))
|
|
68
50
|
|
|
51
|
+
body = doc.getBody()
|
|
52
|
+
body.appendListItem('l1 append')
|
|
53
|
+
doc = scl(doc)
|
|
54
|
+
console.log(report(Docs.Documents.get(id), `\nl1.appended list item l1`))
|
|
55
|
+
|
|
56
|
+
|
|
69
57
|
body = doc.getBody()
|
|
70
58
|
body.insertParagraph(0, 'para 0')
|
|
71
59
|
doc = scl(doc)
|
|
@@ -131,8 +119,8 @@ const pbnew = () => {
|
|
|
131
119
|
|
|
132
120
|
deleteTempFile(id)
|
|
133
121
|
}
|
|
134
|
-
pbnew()
|
|
135
122
|
|
|
123
|
+
pbnew()
|
|
136
124
|
|
|
137
125
|
|
|
138
126
|
|
|
@@ -2,5 +2,11 @@ import "../../main.js";
|
|
|
2
2
|
import { FakeSpreadsheetApp } from "../../src/services/spreadsheetapp/fakespreadsheetapp.js";
|
|
3
3
|
import { moveToTempFolder, deleteTempFile } from "./tempfolder.js";
|
|
4
4
|
|
|
5
|
-
const spreadsheet = SpreadsheetApp.openById("");
|
|
5
|
+
const spreadsheet = SpreadsheetApp.openById("###");
|
|
6
6
|
const sheet = spreadsheet.getSheets()[0];
|
|
7
|
+
sheet.setName("sample");
|
|
8
|
+
|
|
9
|
+
const r = spreadsheet.getRangeByName("test111");
|
|
10
|
+
if (r) {
|
|
11
|
+
console.log(r.getA1Notation());
|
|
12
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import "../../main.js";
|
|
2
|
+
import { FakeSpreadsheetApp } from "../../src/services/spreadsheetapp/fakespreadsheetapp.js";
|
|
3
|
+
import { moveToTempFolder, deleteTempFile } from "./tempfolder.js";
|
|
4
|
+
|
|
5
|
+
const spreadsheet = SpreadsheetApp.create("sample");
|
|
6
|
+
const sheet = spreadsheet.getSheets()[0];
|
|
7
|
+
const spreadsheetId = spreadsheet.getId();
|
|
8
|
+
moveToTempFolder(spreadsheetId);
|
|
9
|
+
|
|
10
|
+
// Create named ranges
|
|
11
|
+
const name1 = "sampleNamedRange1";
|
|
12
|
+
const range1 = sheet.getRange("A1:B2");
|
|
13
|
+
const name2 = "sampleNamedRange2";
|
|
14
|
+
const range2 = sheet.getRange("C1:E2");
|
|
15
|
+
spreadsheet.setNamedRange(name1, range1);
|
|
16
|
+
spreadsheet.setNamedRange(name2, range2);
|
|
17
|
+
|
|
18
|
+
// Get created named ranges from Spreadsheet
|
|
19
|
+
const res1 = spreadsheet
|
|
20
|
+
.getNamedRanges()
|
|
21
|
+
.map(
|
|
22
|
+
(rr) =>
|
|
23
|
+
`${rr.getName()}: '${rr.getRange().getSheet().getSheetName()}'!${rr
|
|
24
|
+
.getRange()
|
|
25
|
+
.getA1Notation()}`
|
|
26
|
+
);
|
|
27
|
+
console.log(res1);
|
|
28
|
+
|
|
29
|
+
// Get created named ranges from Sheet
|
|
30
|
+
const res2 = sheet
|
|
31
|
+
.getNamedRanges()
|
|
32
|
+
.map(
|
|
33
|
+
(rr) =>
|
|
34
|
+
`${rr.getName()}: '${rr.getRange().getSheet().getSheetName()}'!${rr
|
|
35
|
+
.getRange()
|
|
36
|
+
.getA1Notation()}`
|
|
37
|
+
);
|
|
38
|
+
console.log(res2);
|
|
39
|
+
|
|
40
|
+
// Update named ranges.
|
|
41
|
+
spreadsheet.getNamedRanges().forEach((r, i) => {
|
|
42
|
+
r.setName(`${r.getName()}_${i + 10}`);
|
|
43
|
+
const range = r.getRange().offset(0, 2);
|
|
44
|
+
r.setRange(range);
|
|
45
|
+
});
|
|
46
|
+
const res3 = spreadsheet
|
|
47
|
+
.getNamedRanges()
|
|
48
|
+
.map(
|
|
49
|
+
(rr) =>
|
|
50
|
+
`${rr.getName()}: '${rr.getRange().getSheet().getSheetName()}'!${rr
|
|
51
|
+
.getRange()
|
|
52
|
+
.getA1Notation()}`
|
|
53
|
+
);
|
|
54
|
+
console.log(res3);
|
|
55
|
+
|
|
56
|
+
// Remove named ranges
|
|
57
|
+
spreadsheet.getNamedRanges().forEach((r) => r.remove());
|
|
58
|
+
|
|
59
|
+
deleteTempFile(spreadsheetId);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# A shell script to create a GitHub issue for the sandbox feature.
|
|
4
|
+
|
|
5
|
+
# Issue title
|
|
6
|
+
TITLE="Improve Sandbox Mode to fully emulate drive.file scope"
|
|
7
|
+
|
|
8
|
+
# Issue body in markdown
|
|
9
|
+
BODY=$(cat <<'EOF'
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
The current implementation of `gas-fakes` requires the full `https://www.googleapis.com/auth/drive` scope for initialization, primarily to access metadata about the root Drive folder. This is a stumbling block for users who want to run tests in a more restrictive environment using only the `drive.file` scope.
|
|
13
|
+
|
|
14
|
+
The `ScriptApp.__behavior.sandBoxMode` was introduced as a clever workaround to *emulate* the `drive.file` scope during test execution, but it doesn't solve the initial authorization requirement.
|
|
15
|
+
|
|
16
|
+
For more details on the current sandbox implementation, see sandbox.md.
|
|
17
|
+
|
|
18
|
+
## TODO
|
|
19
|
+
|
|
20
|
+
We need to investigate ways to remove the dependency on root folder access during the `FakeDriveApp` initialization. This would allow `gas-fakes` to operate with only the `drive.file` scope, making the sandbox feature a true end-to-end emulation.
|
|
21
|
+
|
|
22
|
+
Possible areas to explore:
|
|
23
|
+
- Can we mock or bypass the root folder check during initialization if a specific `drive.file` mode is detected?
|
|
24
|
+
- Can we lazy-initialize parts of `FakeDriveApp` that require root access, and only fail if those specific methods (like `getRootFolder()`) are called without sufficient permissions?
|
|
25
|
+
|
|
26
|
+
This would be a significant improvement for security-conscious development and testing workflows.
|
|
27
|
+
EOF
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Create the issue using GitHub CLI
|
|
31
|
+
gh issue create --title "$TITLE" --body "$BODY" --label "enhancement" --label "help wanted"
|
package/package.json
CHANGED
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
},
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@mcpher/fake-gasenum": "^1.0.2",
|
|
7
|
+
"@mcpher/gas-fakes": "^1.0.14",
|
|
7
8
|
"@mcpher/unit": "^1.1.11",
|
|
8
9
|
"@sindresorhus/is": "^7.0.1",
|
|
9
10
|
"archiver": "^7.0.1",
|
|
10
11
|
"get-stream": "^9.0.1",
|
|
11
|
-
"googleapis": "^
|
|
12
|
-
"got": "^14.4.
|
|
12
|
+
"googleapis": "^157.0.0",
|
|
13
|
+
"got": "^14.4.7",
|
|
13
14
|
"into-stream": "^8.0.1",
|
|
14
|
-
"keyv": "^5.
|
|
15
|
-
"keyv-file": "^5.1.
|
|
16
|
-
"mime": "^4.0.
|
|
15
|
+
"keyv": "^5.5.0",
|
|
16
|
+
"keyv-file": "^5.1.3",
|
|
17
|
+
"mime": "^4.0.7",
|
|
17
18
|
"sleep-synchronously": "^2.0.0",
|
|
18
19
|
"subsume": "^4.0.0",
|
|
19
20
|
"to-readable-stream": "^4.0.0",
|
|
@@ -45,10 +46,13 @@
|
|
|
45
46
|
"testslides": "cp mainlocal.js main.js && node --env-file=.env ./test/testslides.js execute",
|
|
46
47
|
"testdocsnext": "cp mainlocal.js main.js && node --env-file=.env ./test/testdocsnext.js execute",
|
|
47
48
|
"testsheetstext": "cp mainlocal.js main.js && node --env-file=.env ./test/testsheetstext.js execute",
|
|
49
|
+
"testsheetsrange": "cp mainlocal.js main.js && node --env-file=.env ./test/testsheetsrange.js execute",
|
|
50
|
+
"testdocslistitems": "cp mainlocal.js main.js && node --env-file=.env ./test/testdocslistitems.js execute",
|
|
51
|
+
"testdocsall": "cp mainlocal.js main.js && node --env-file=.env ./test/testdocsall.js",
|
|
48
52
|
"pub": "cp mainlocal.js main.js && npm publish --access public"
|
|
49
53
|
},
|
|
50
54
|
"name": "@mcpher/gas-fakes",
|
|
51
|
-
"version": "1.0.
|
|
55
|
+
"version": "1.0.15",
|
|
52
56
|
"license": "MIT",
|
|
53
57
|
"main": "main.js",
|
|
54
58
|
"description": "A proof of concept implementation of Apps Script Environment on Node",
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { google } from "googleapis";
|
|
2
2
|
import { Auth } from '../../support/auth.js'
|
|
3
|
+
import { syncLog} from '../../support/workersync/synclogger.js'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
return google.docs({ version: 'v1', auth });
|
|
6
|
-
}
|
|
5
|
+
let __client = null;
|
|
7
6
|
|
|
8
|
-
export const
|
|
7
|
+
export const getDocsApiClient = () => {
|
|
9
8
|
const auth = Auth.getAuth()
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (!__client) {
|
|
10
|
+
syncLog('Creating new Docs API client');
|
|
11
|
+
__client = google.docs({ version: 'v1', auth });
|
|
12
|
+
}
|
|
13
|
+
return __client;
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
* Advanced sheets service
|
|
3
3
|
*/
|
|
4
4
|
import { Proxies } from '../../support/proxies.js'
|
|
5
|
-
import { advClassMaker
|
|
6
|
-
import { getAuthedClient } from './docapis.js'
|
|
5
|
+
import { advClassMaker } from '../../support/helpers.js'
|
|
7
6
|
import { newFakeAdvDocuments } from './fakeadvdocuments.js'
|
|
8
7
|
import { docsCacher } from '../../support/docscacher.js';
|
|
9
8
|
|
|
10
9
|
class FakeAdvDocs {
|
|
11
10
|
constructor() {
|
|
12
|
-
this.client = Proxies.guard(getAuthedClient())
|
|
13
11
|
this.__fakeObjectType = "Docs"
|
|
14
12
|
|
|
15
13
|
const propLists = {
|
|
@@ -9,6 +9,7 @@ import { Syncit } from '../../support/syncit.js'
|
|
|
9
9
|
import is from '@sindresorhus/is'
|
|
10
10
|
import { FakeAdvResource } from '../common/fakeadvresource.js';
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
/**
|
|
13
14
|
* the advanced docs Apps Script service faked - Documents class
|
|
14
15
|
* @class FakeAdvDocuments
|
|
@@ -18,6 +19,19 @@ class FakeAdvDocuments extends FakeAdvResource {
|
|
|
18
19
|
super(docs, 'documents', Syncit.fxDocs);
|
|
19
20
|
this.__fakeObjectType = "Docs.Documents";
|
|
20
21
|
}
|
|
22
|
+
// in sandbox mode only files created in this instance are
|
|
23
|
+
__allowed(id) {
|
|
24
|
+
if (!ScriptApp.__behavior.isAccessible(id)) {
|
|
25
|
+
throw new Error(`Access to document ${id} is not allowed in sandbox mode`);
|
|
26
|
+
}
|
|
27
|
+
return id
|
|
28
|
+
}
|
|
29
|
+
__addAllowed(id) {
|
|
30
|
+
if (ScriptApp.__behavior.sandBoxMode) {
|
|
31
|
+
ScriptApp.__behavior.addFile(id);
|
|
32
|
+
}
|
|
33
|
+
return id
|
|
34
|
+
}
|
|
21
35
|
|
|
22
36
|
create(resource, options) {
|
|
23
37
|
const { nargs, matchThrow } = signatureArgs(arguments, "Docs.Documents.create")
|
|
@@ -31,7 +45,7 @@ class FakeAdvDocuments extends FakeAdvResource {
|
|
|
31
45
|
|
|
32
46
|
// maybe we need to throw an error
|
|
33
47
|
ssError(response, "create")
|
|
34
|
-
|
|
48
|
+
this.__addAllowed(data.documentId);
|
|
35
49
|
return data
|
|
36
50
|
}
|
|
37
51
|
|
|
@@ -42,11 +56,12 @@ class FakeAdvDocuments extends FakeAdvResource {
|
|
|
42
56
|
matchThrow("API call to docs.documents.batchUpdate failed with error: Invalid JSON payload received.");
|
|
43
57
|
}
|
|
44
58
|
|
|
59
|
+
|
|
45
60
|
// Invalidate the cache for this document since we are updating it.
|
|
46
61
|
docsCacher.clear(documentId);
|
|
47
62
|
// console.log (JSON.stringify(requests))
|
|
48
63
|
const { response, data } = this._call("batchUpdate", {
|
|
49
|
-
documentId: documentId,
|
|
64
|
+
documentId: this.__allowed(documentId),
|
|
50
65
|
requestBody: requests
|
|
51
66
|
});
|
|
52
67
|
|
|
@@ -87,7 +102,7 @@ class FakeAdvDocuments extends FakeAdvResource {
|
|
|
87
102
|
|
|
88
103
|
if (is.nonEmptyObject(options) && !Reflect.ownKeys(options || {}).every(f => optionsSet.has(f))) matchThrow();
|
|
89
104
|
|
|
90
|
-
const params = { documentId, ...(options || {}) };
|
|
105
|
+
const params = {documentId: this.__allowed(documentId), ...(options || {}) };
|
|
91
106
|
|
|
92
107
|
const { response, data } = this._call("get", params);
|
|
93
108
|
ssError(response, 'get');
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { google } from "googleapis";
|
|
2
2
|
import { Auth } from '../../support/auth.js'
|
|
3
|
+
import { syncLog} from '../../support/workersync/synclogger.js'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export const getAuthedClient = () => {
|
|
5
|
+
let __client = null;
|
|
6
|
+
syncLog('...importing Drive API');
|
|
7
|
+
export const getDriveApiClient = () => {
|
|
9
8
|
const auth = Auth.getAuth()
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (!__client) {
|
|
10
|
+
syncLog('Creating new Drive API client');
|
|
11
|
+
__client = google.drive({ version: 'v3', auth });
|
|
12
|
+
}
|
|
13
|
+
return __client;
|
|
14
|
+
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Proxies } from '../../support/proxies.js'
|
|
5
5
|
import { notYetImplemented } from '../../support/helpers.js'
|
|
6
|
-
import { getAuthedClient } from './drapis.js'
|
|
7
6
|
import { newFakeAdvDriveAbout } from './fakeadvdriveabout.js'
|
|
8
7
|
import { newFakeAdvDriveFiles } from './fakeadvdrivefiles.js';
|
|
9
8
|
import { newFakeAdvDriveApps } from './fakeadvdriveapps.js'
|
|
@@ -17,7 +16,6 @@ import { getDrivePerformance } from '../../support/filecache.js';
|
|
|
17
16
|
|
|
18
17
|
class FakeAdvDrive {
|
|
19
18
|
constructor() {
|
|
20
|
-
this.client = Proxies.guard(getAuthedClient())
|
|
21
19
|
this.__fakeObjectType = "Drive"
|
|
22
20
|
}
|
|
23
21
|
toString() {
|
|
@@ -71,6 +69,25 @@ class FakeAdvDrive {
|
|
|
71
69
|
__getDrivePerformance() {
|
|
72
70
|
return getDrivePerformance()
|
|
73
71
|
}
|
|
72
|
+
// in sandbox mode only files created in this instance are
|
|
73
|
+
__allowed(id) {
|
|
74
|
+
// initially we will allow access to root here
|
|
75
|
+
// TODO - need to find a way to work without a root required in DriveApp
|
|
76
|
+
if (id === 'root') {
|
|
77
|
+
return id
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!ScriptApp.__behavior.isAccessible(id)) {
|
|
81
|
+
throw new Error(`Access to file ${id} is not allowed in sandbox mode`);
|
|
82
|
+
}
|
|
83
|
+
return id
|
|
84
|
+
}
|
|
85
|
+
__addAllowed(id) {
|
|
86
|
+
if (ScriptApp.__behavior.sandBoxMode) {
|
|
87
|
+
ScriptApp.__behavior.addFile(id);
|
|
88
|
+
}
|
|
89
|
+
return id
|
|
90
|
+
}
|
|
74
91
|
|
|
75
92
|
}
|
|
76
93
|
|
|
@@ -52,6 +52,7 @@ class FakeAdvDriveFiles {
|
|
|
52
52
|
/// extract out the fieldslist
|
|
53
53
|
const fields = params.fields.replace(/.*files\(([^)]*)\).*/,"$1")
|
|
54
54
|
|
|
55
|
+
/// TODO - filter out any files we wouldn't be allowed to see in sandbox mode
|
|
55
56
|
data.files.forEach(f => {
|
|
56
57
|
improveFileCache(f.id, f,fields)
|
|
57
58
|
})
|
|
@@ -114,7 +115,7 @@ class FakeAdvDriveFiles {
|
|
|
114
115
|
* @returns {Drive.File}
|
|
115
116
|
*/
|
|
116
117
|
get(id, params = {}, { allow404 = true } = {}) {
|
|
117
|
-
const {data} = Syncit.fxDriveGet ({ id, params, allow404, allowCache: true })
|
|
118
|
+
const {data} = Syncit.fxDriveGet ({ id: this.drive.__allowed(id), prop: apiProp, method: 'get', params, allow404, allowCache: true })
|
|
118
119
|
return data
|
|
119
120
|
}
|
|
120
121
|
|
|
@@ -132,7 +133,9 @@ class FakeAdvDriveFiles {
|
|
|
132
133
|
|
|
133
134
|
// must have some kind of name so derive if not given
|
|
134
135
|
const name = file.name || blob?.getName() || (isFolder(file) ? "New Folder" : "Untitled")
|
|
135
|
-
|
|
136
|
+
const d = updateOrCreate ({method: "create", file: { ...file, name }, blob, fields, params })
|
|
137
|
+
this.drive.__addAllowed(d.id);
|
|
138
|
+
return d
|
|
136
139
|
|
|
137
140
|
}
|
|
138
141
|
|
|
@@ -153,7 +156,7 @@ class FakeAdvDriveFiles {
|
|
|
153
156
|
if (!is.nonEmptyString(fileId)) {
|
|
154
157
|
throw new Error(`API call to drive.files.update failed with error: Required`)
|
|
155
158
|
}
|
|
156
|
-
return updateOrCreate ({method: 'update', file, blob, fileId , fields, params})
|
|
159
|
+
return updateOrCreate ({method: 'update', file, blob, fileId: this.drive.__allowed(fileId) , fields, params})
|
|
157
160
|
}
|
|
158
161
|
/**
|
|
159
162
|
* ceate a file and optionally upload some data
|
|
@@ -169,13 +172,13 @@ class FakeAdvDriveFiles {
|
|
|
169
172
|
const fields = mergeParamStrings(options.fields || "",minFields)
|
|
170
173
|
const params = {
|
|
171
174
|
fields,
|
|
172
|
-
fileId,
|
|
175
|
+
fileId: this.drive.__allowed(fileId),
|
|
173
176
|
resource: file
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
const { response, data } = Syncit.fxDrive({ prop: apiProp, method: 'copy', params, options })
|
|
177
180
|
checkResponse(data?.id, response, false)
|
|
178
|
-
improveFileCache(data.id, data,fields)
|
|
181
|
+
improveFileCache(this.drive.__addAllowed(data.id), data,fields)
|
|
179
182
|
return data
|
|
180
183
|
|
|
181
184
|
}
|
|
@@ -8,7 +8,7 @@ class FakeAdvDrivePermissions extends FakeAdvResource {
|
|
|
8
8
|
// The service name is 'permissions', and it uses the main Drive sync method.
|
|
9
9
|
super(drive, 'permissions', Syncit.fxDrive);
|
|
10
10
|
this.__fakeObjectType = "Drive.Permissions";
|
|
11
|
-
|
|
11
|
+
this.drive = drive;
|
|
12
12
|
const props = ['get', 'update'];
|
|
13
13
|
props.forEach(f => {
|
|
14
14
|
if (!this[f]) {
|
|
@@ -23,7 +23,7 @@ class FakeAdvDrivePermissions extends FakeAdvResource {
|
|
|
23
23
|
|
|
24
24
|
const params = {
|
|
25
25
|
resource,
|
|
26
|
-
fileId,
|
|
26
|
+
fileId: this.drive.__allowed(fileId),
|
|
27
27
|
...(optionalArgs || {})
|
|
28
28
|
};
|
|
29
29
|
const { data } = this._call('create', params);
|
|
@@ -35,7 +35,7 @@ class FakeAdvDrivePermissions extends FakeAdvResource {
|
|
|
35
35
|
if (nargs < 2 || nargs > 3) matchThrow();
|
|
36
36
|
|
|
37
37
|
const params = {
|
|
38
|
-
fileId,
|
|
38
|
+
fileId: this.drive.__allowed(fileId),
|
|
39
39
|
permissionId,
|
|
40
40
|
...(optionalArgs || {})
|
|
41
41
|
};
|
|
@@ -47,7 +47,7 @@ class FakeAdvDrivePermissions extends FakeAdvResource {
|
|
|
47
47
|
const { nargs, matchThrow } = signatureArgs(arguments, "Drive.Permissions.list");
|
|
48
48
|
if (nargs < 1 || nargs > 2) matchThrow();
|
|
49
49
|
|
|
50
|
-
const params = { fileId, ...(optionalArgs || {}) };
|
|
50
|
+
const params = { fileId: this.drive.__allowed(fileId), ...(optionalArgs || {}) };
|
|
51
51
|
const { data } = this._call('list', params);
|
|
52
52
|
return data;
|
|
53
53
|
}
|
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Proxies } from '../../support/proxies.js'
|
|
5
5
|
import { advClassMaker } from '../../support/helpers.js'
|
|
6
|
-
import { getAuthedClient } from './shapis.js'
|
|
7
6
|
import { newFakeAdvSheetsSpreadsheets } from './fakeadvsheetsspreadsheets.js'
|
|
8
7
|
import { sheetsCacher } from '../../support/sheetscacher.js';
|
|
9
|
-
|
|
8
|
+
|
|
10
9
|
|
|
11
10
|
class FakeAdvSheets {
|
|
12
11
|
constructor() {
|
|
13
|
-
this.
|
|
14
|
-
this.__fakeObjectType = "Docs"
|
|
12
|
+
this.__fakeObjectType = "Sheets"
|
|
15
13
|
|
|
16
14
|
const propLists = {
|
|
17
15
|
newGridRange: ['sheetId', 'startRowIndex', 'startColumnIndex', 'endRowIndex', 'endColumnIndex'],
|
|
@@ -880,7 +878,19 @@ class FakeAdvSheets {
|
|
|
880
878
|
__getSheetsPerformance() {
|
|
881
879
|
return sheetsCacher.getPerformance()
|
|
882
880
|
}
|
|
881
|
+
// in sandbox mode only files created in this instance are
|
|
882
|
+
__allowed(id) {
|
|
883
|
+
if (!ScriptApp.__behavior.isAccessible(id)) {
|
|
884
|
+
throw new Error(`Access to document ${id} is not allowed in sandbox mode`);
|
|
885
|
+
}
|
|
886
|
+
return id
|
|
887
|
+
}
|
|
888
|
+
__addAllowed(id) {
|
|
889
|
+
if (ScriptApp.__behavior.sandBoxMode) {
|
|
890
|
+
ScriptApp.__behavior.addFile(id);
|
|
891
|
+
}
|
|
892
|
+
return id
|
|
893
|
+
}
|
|
883
894
|
}
|
|
884
895
|
|
|
885
896
|
export const newFakeAdvSheets = (...args) => Proxies.guard(new FakeAdvSheets(...args))
|
|
886
|
-
|