@mcpher/gas-fakes 1.0.2 → 1.0.4
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 +26 -28
- package/package.json +4 -2
- package/src/index.js +2 -0
- package/src/services/advdrive/app.js +31 -0
- package/src/services/advdrive/fakeadvdrive.js +252 -0
- package/src/services/scriptapp/app.js +1 -1
- package/src/services/session/app.js +33 -0
- package/src/services/session/fakesession.js +45 -0
- package/src/services/session/fakeuser.js +32 -0
- package/src/services/sheets/app.js +2 -1
- package/src/services/stores/app.js +2 -1
- package/src/services/stores/fakestores.js +2 -2
- package/src/services/urlfetchapp/app.js +2 -1
- package/src/services/utilities/app.js +5 -20
- package/src/services/utilities/fakeblob.js +4 -4
- package/src/services/utilities/fakeutilities.js +105 -0
- package/src/support/auth.js +17 -3
- package/src/support/constants.js +5 -0
- package/src/support/syncit.js +143 -25
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ gasfakes.json holds various location and behavior parameters to inform about you
|
|
|
36
36
|
"documentId": null,
|
|
37
37
|
"cache": "/tmp/gas-fakes/cache",
|
|
38
38
|
"properties": "/tmp/gas-fakes/properties",
|
|
39
|
-
"scriptId": "
|
|
39
|
+
"scriptId": "1bc79bd3-fe02-425f-9653-525e5ae0b678"
|
|
40
40
|
}
|
|
41
41
|
````
|
|
42
42
|
| property | type | default | description |
|
|
@@ -157,9 +157,9 @@ I recommend you do this to make sure Auth it's all good before you start coding
|
|
|
157
157
|
|
|
158
158
|
### Global intialization
|
|
159
159
|
|
|
160
|
-
This was a little problematic to sequence, but I wanted to make sure that any GAS services being imitated were available and initialized on the Node side, just as they are in GAS. At the time of writing these services are implemented.
|
|
160
|
+
This was a little problematic to sequence, but I wanted to make sure that any GAS services being imitated were available and initialized on the Node side, just as they are in GAS. At the time of writing these services and classes are implemented. However, only a subset of methods are currently available for some of them - the rest are work in progress.
|
|
161
161
|
|
|
162
|
-
v1.0.
|
|
162
|
+
v1.0.3
|
|
163
163
|
- `DriveApp`
|
|
164
164
|
- `ScriptApp`
|
|
165
165
|
- `UrlFetchApp`
|
|
@@ -167,49 +167,43 @@ v1.0.1
|
|
|
167
167
|
- `Sheets`
|
|
168
168
|
- `CacheService`
|
|
169
169
|
- `PropertiesService`
|
|
170
|
+
- `Session`
|
|
171
|
+
- `Blob`
|
|
172
|
+
- `User`
|
|
170
173
|
|
|
171
174
|
#### Proxies and globalThis
|
|
172
175
|
|
|
173
176
|
Each service has a FakeClass but I needed the Auth cycle to be initiated and done before making them public. Using a proxy was the simplest approach.
|
|
174
177
|
|
|
175
|
-
Here's the code for `
|
|
178
|
+
Here's the code for `Utilities`
|
|
176
179
|
|
|
177
180
|
```js
|
|
178
181
|
|
|
179
182
|
/**
|
|
180
183
|
* adds to global space to mimic Apps Script behavior
|
|
181
184
|
*/
|
|
182
|
-
|
|
185
|
+
import { Proxies } from '../../support/proxies.js'
|
|
186
|
+
import { newFakeUtilities } from './fakeutilities.js';
|
|
183
187
|
|
|
184
|
-
if (typeof globalThis[name] === typeof undefined) {
|
|
185
188
|
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
// This will eventually hold a proxy for Utilties
|
|
190
|
+
let _app = null
|
|
188
191
|
|
|
189
|
-
|
|
192
|
+
/**
|
|
193
|
+
* adds to global space to mimic Apps Script behavior
|
|
194
|
+
*/
|
|
195
|
+
const name = "Utilities"
|
|
196
|
+
if (typeof globalThis[name] === typeof undefined) {
|
|
190
197
|
const getApp = () => {
|
|
191
|
-
|
|
192
|
-
// if it hasn't been intialized yet then do that
|
|
198
|
+
// if it hasnt been intialized yet then do that
|
|
193
199
|
if (!_app) {
|
|
194
|
-
|
|
195
|
-
_app =
|
|
196
|
-
getOAuthToken,
|
|
197
|
-
requireAllScopes,
|
|
198
|
-
requireScopes,
|
|
199
|
-
AuthMode: {
|
|
200
|
-
FULL: 'FULL'
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
200
|
+
console.log (`setting ${name} to global`)
|
|
201
|
+
_app = newFakeUtilities()
|
|
205
202
|
}
|
|
206
203
|
// this is the actual driveApp we'll return from the proxy
|
|
207
204
|
return _app
|
|
208
205
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
Proxies.registerProxy(name, getApp)
|
|
212
|
-
|
|
206
|
+
Proxies.registerProxy (name, getApp)
|
|
213
207
|
}
|
|
214
208
|
```
|
|
215
209
|
|
|
@@ -349,14 +343,18 @@ The local version may have no knowledge of the Apps ScriptId. If you are using c
|
|
|
349
343
|
##### userId
|
|
350
344
|
|
|
351
345
|
The userId is extracted from an accessToken and will match the id derived from Application Default Credentials. This means that you can logon as a different user to test user data isolation. All user level property and cache stores use the scriptId and userId to partition data.
|
|
352
|
-
|
|
353
346
|
##### documentId
|
|
354
347
|
|
|
355
348
|
The documentId is only meaningful if you are working on a container bound scrip. We use the the documentId property of gasfakes.json to identify a container file. All document level property and cache stores use the scriptId and documentId to partition data.
|
|
356
349
|
|
|
357
350
|
### Settings and temporary files
|
|
358
351
|
|
|
359
|
-
As you will have noticed, there are various local support files for props/caching etc. Be careful that these do not get committed to a public repo if you are adding sensitive values to your stores.
|
|
352
|
+
As you will have noticed, there are various local support files for props/caching etc. Be careful that these do not get committed to a public repo if you are adding sensitive values to your stores. Note that the real user Id is not used when creating files, but rather an encrypted version of it. This avoids real user ids being revealed in your file system.
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
## Noticed differences
|
|
356
|
+
|
|
357
|
+
I'll make a note here on trivial implementation differences. The main will be slight differences in error message text, which I'll normalize over time. Please report any differences in behavior you find in the repo issues.
|
|
360
358
|
|
|
361
359
|
|
|
362
360
|
## Help
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@sindresorhus/is": "^7.0.1",
|
|
4
|
+
"archiver": "^7.0.1",
|
|
4
5
|
"get-stream": "^9.0.1",
|
|
5
6
|
"google-auth-library": "^9.15.0",
|
|
6
7
|
"googleapis": "^144.0.0",
|
|
@@ -9,7 +10,8 @@
|
|
|
9
10
|
"keyv-file": "^5.1.1",
|
|
10
11
|
"make-synchronous": "^1.0.0",
|
|
11
12
|
"mime": "^4.0.6",
|
|
12
|
-
"sleep-synchronously": "^2.0.0"
|
|
13
|
+
"sleep-synchronously": "^2.0.0",
|
|
14
|
+
"unzipper": "^0.12.3"
|
|
13
15
|
},
|
|
14
16
|
"type": "module",
|
|
15
17
|
"scripts": {
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
"pub": "npm publish --access public"
|
|
18
20
|
},
|
|
19
21
|
"name": "@mcpher/gas-fakes",
|
|
20
|
-
"version": "1.0.
|
|
22
|
+
"version": "1.0.4",
|
|
21
23
|
"main": "main.js",
|
|
22
24
|
"description": "A proof of concept implementation of Apps Script Environment on Node",
|
|
23
25
|
"repository": "github:brucemcpherson/gas-fakes",
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// fake Apps Script DriveApp
|
|
2
|
+
/**
|
|
3
|
+
* the idea here is to create a global entry for the singleton
|
|
4
|
+
* before we actually have everything we need to create it.
|
|
5
|
+
* We do this by using a proxy, intercepting calls to the
|
|
6
|
+
* initial sigleton and diverting them to a completed one
|
|
7
|
+
*/
|
|
8
|
+
import { newFakeAdvDrive} from './fakeadvdrive.js'
|
|
9
|
+
import { Proxies } from '../../support/proxies.js'
|
|
10
|
+
|
|
11
|
+
// This will eventually hold a proxy for DriveApp
|
|
12
|
+
let _app = null
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* adds to global space to mimic Apps Script behavior
|
|
16
|
+
*/
|
|
17
|
+
const name = "Drive"
|
|
18
|
+
if (typeof globalThis[name] === typeof undefined) {
|
|
19
|
+
|
|
20
|
+
const getApp = () => {
|
|
21
|
+
// if it hasne been intialized yet then do that
|
|
22
|
+
if (!_app) {
|
|
23
|
+
_app = newFakeAdvDrive()
|
|
24
|
+
}
|
|
25
|
+
// this is the actual driveApp we'll return from the proxy
|
|
26
|
+
return _app
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Proxies.registerProxy (name, getApp)
|
|
30
|
+
|
|
31
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced drive service
|
|
3
|
+
*/
|
|
4
|
+
import { Proxies } from '../../support/proxies.js'
|
|
5
|
+
import { notYetImplemented } from '../../support/constants.js'
|
|
6
|
+
|
|
7
|
+
class FakeAdvDrive {
|
|
8
|
+
constructor() {
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
toString() {
|
|
12
|
+
return `AdvancedServiceIdentifier{name=drive, version=v3}`
|
|
13
|
+
}
|
|
14
|
+
getVersion() {
|
|
15
|
+
return 'v3'
|
|
16
|
+
}
|
|
17
|
+
get Files() {
|
|
18
|
+
return newFakeAdvDriveFiles(this)
|
|
19
|
+
}
|
|
20
|
+
get About() {
|
|
21
|
+
return newFakeAdvDriveAbout(this)
|
|
22
|
+
}
|
|
23
|
+
get Accessproposals() {
|
|
24
|
+
return notYetImplemented
|
|
25
|
+
}
|
|
26
|
+
get Apps() {
|
|
27
|
+
return notYetImplemented
|
|
28
|
+
}
|
|
29
|
+
get Changes() {
|
|
30
|
+
return notYetImplemented
|
|
31
|
+
}
|
|
32
|
+
get Channels() {
|
|
33
|
+
return notYetImplemented
|
|
34
|
+
}
|
|
35
|
+
get Comments() {
|
|
36
|
+
return notYetImplemented
|
|
37
|
+
}
|
|
38
|
+
get Drives() {
|
|
39
|
+
return notYetImplemented
|
|
40
|
+
}
|
|
41
|
+
get Operations() {
|
|
42
|
+
return notYetImplemented
|
|
43
|
+
}
|
|
44
|
+
get Permissions() {
|
|
45
|
+
return notYetImplemented
|
|
46
|
+
}
|
|
47
|
+
get Replies() {
|
|
48
|
+
return notYetImplemented
|
|
49
|
+
}
|
|
50
|
+
get Revisions() {
|
|
51
|
+
return notYetImplemented
|
|
52
|
+
}
|
|
53
|
+
get Teamdrives() {
|
|
54
|
+
return notYetImplemented
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class FakeAdvDriveAbout {
|
|
60
|
+
constructor(drive) {
|
|
61
|
+
this.toString = drive.toString
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// this is a schema and needs the fields parameter
|
|
65
|
+
get() {
|
|
66
|
+
return notYetImplemented
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class FakeAdvDriveFiles {
|
|
71
|
+
constructor(drive) {
|
|
72
|
+
this.toString = drive.toString
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
listLabels() {
|
|
76
|
+
return notYetImplemented
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
emptyTrash() {
|
|
80
|
+
return notYetImplemented
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
update() {
|
|
84
|
+
return notYetImplemented
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
list() {
|
|
88
|
+
return notYetImplemented
|
|
89
|
+
}
|
|
90
|
+
remove() {
|
|
91
|
+
return notYetImplemented
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
download() {
|
|
95
|
+
return notYetImplemented
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
modifyLabels() {
|
|
99
|
+
return notYetImplemented
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
watch() {
|
|
103
|
+
return notYetImplemented
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get() {
|
|
107
|
+
return notYetImplemented
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
create() {
|
|
111
|
+
return notYetImplemented
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
generateIds() {
|
|
115
|
+
return notYetImplemented
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
copy() {
|
|
119
|
+
return notYetImplemented
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export() {
|
|
123
|
+
return notYetImplemented
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const newFakeAdvDriveFiles = (...args) => Proxies.guard(new FakeAdvDriveFiles(...args))
|
|
129
|
+
const newFakeAdvDriveAbout = (...args) => Proxies.guard(new FakeAdvDriveAbout(...args))
|
|
130
|
+
export const newFakeAdvDrive = (...args) => Proxies.guard(new FakeAdvDrive)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
/* methods to implement
|
|
135
|
+
toString: [Function],
|
|
136
|
+
getVersion: [Function],
|
|
137
|
+
newTeamDriveRestrictions: [Function],
|
|
138
|
+
newTeamDrive: [Function],
|
|
139
|
+
newLabelFieldModification: [Function],
|
|
140
|
+
newFileImageMediaMetadataLocation: [Function],
|
|
141
|
+
newRevision: [Function],
|
|
142
|
+
newComment: [Function],
|
|
143
|
+
newFile: [Function],
|
|
144
|
+
newContentRestriction: [Function],
|
|
145
|
+
newDrive: [Function],
|
|
146
|
+
newDriveCapabilities: [Function],
|
|
147
|
+
newFileVideoMediaMetadata: [Function],
|
|
148
|
+
newDriveBackgroundImageFile: [Function],
|
|
149
|
+
newResolveAccessProposalRequest: [Function],
|
|
150
|
+
newFileLabelInfo: [Function],
|
|
151
|
+
newTeamDriveBackgroundImageFile: [Function],
|
|
152
|
+
newFileContentHints: [Function],
|
|
153
|
+
newPermission: [Function],
|
|
154
|
+
newFileLinkShareMetadata: [Function],
|
|
155
|
+
newFileImageMediaMetadata: [Function],
|
|
156
|
+
newFileCapabilities: [Function],
|
|
157
|
+
newCommentQuotedFileContent: [Function],
|
|
158
|
+
newReply: [Function],
|
|
159
|
+
newFileContentHintsThumbnail: [Function],
|
|
160
|
+
newModifyLabelsRequest: [Function],
|
|
161
|
+
newUser: [Function],
|
|
162
|
+
newLabel: [Function],
|
|
163
|
+
newDownloadRestriction: [Function],
|
|
164
|
+
newLabelModification: [Function],
|
|
165
|
+
newPermissionPermissionDetails: [Function],
|
|
166
|
+
newDriveRestrictions: [Function],
|
|
167
|
+
newPermissionTeamDrivePermissionDetails: [Function],
|
|
168
|
+
newFileShortcutDetails: [Function],
|
|
169
|
+
newChannel: [Function],
|
|
170
|
+
newTeamDriveCapabilities: [Function],
|
|
171
|
+
About: { toString: [Function], get: [Function] },
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
Accessproposals:
|
|
177
|
+
{ toString: [Function],
|
|
178
|
+
resolve: [Function],
|
|
179
|
+
get: [Function],
|
|
180
|
+
list: [Function] },
|
|
181
|
+
Apps: { toString: [Function], get: [Function], list: [Function] },
|
|
182
|
+
Changes:
|
|
183
|
+
{ toString: [Function],
|
|
184
|
+
getStartPageToken: [Function],
|
|
185
|
+
watch: [Function],
|
|
186
|
+
list: [Function] },
|
|
187
|
+
Channels: { toString: [Function], stop: [Function] },
|
|
188
|
+
Comments:
|
|
189
|
+
{ toString: [Function],
|
|
190
|
+
get: [Function],
|
|
191
|
+
create: [Function],
|
|
192
|
+
update: [Function],
|
|
193
|
+
list: [Function],
|
|
194
|
+
remove: [Function] },
|
|
195
|
+
Drives:
|
|
196
|
+
{ toString: [Function],
|
|
197
|
+
hide: [Function],
|
|
198
|
+
get: [Function],
|
|
199
|
+
create: [Function],
|
|
200
|
+
update: [Function],
|
|
201
|
+
list: [Function],
|
|
202
|
+
remove: [Function],
|
|
203
|
+
unhide: [Function] },
|
|
204
|
+
Files:
|
|
205
|
+
{ toString: [Function],
|
|
206
|
+
listLabels: [Function],
|
|
207
|
+
emptyTrash: [Function],
|
|
208
|
+
update: [Function],
|
|
209
|
+
list: [Function],
|
|
210
|
+
remove: [Function],
|
|
211
|
+
download: [Function],
|
|
212
|
+
modifyLabels: [Function],
|
|
213
|
+
watch: [Function],
|
|
214
|
+
get: [Function],
|
|
215
|
+
create: [Function],
|
|
216
|
+
generateIds: [Function],
|
|
217
|
+
copy: [Function],
|
|
218
|
+
export: [Function] },
|
|
219
|
+
Operations:
|
|
220
|
+
{ toString: [Function],
|
|
221
|
+
cancel: [Function],
|
|
222
|
+
get: [Function],
|
|
223
|
+
list: [Function],
|
|
224
|
+
remove: [Function] },
|
|
225
|
+
Permissions:
|
|
226
|
+
{ toString: [Function],
|
|
227
|
+
get: [Function],
|
|
228
|
+
create: [Function],
|
|
229
|
+
update: [Function],
|
|
230
|
+
list: [Function],
|
|
231
|
+
remove: [Function] },
|
|
232
|
+
Replies:
|
|
233
|
+
{ toString: [Function],
|
|
234
|
+
get: [Function],
|
|
235
|
+
create: [Function],
|
|
236
|
+
update: [Function],
|
|
237
|
+
list: [Function],
|
|
238
|
+
remove: [Function] },
|
|
239
|
+
Revisions:
|
|
240
|
+
{ toString: [Function],
|
|
241
|
+
get: [Function],
|
|
242
|
+
update: [Function],
|
|
243
|
+
list: [Function],
|
|
244
|
+
remove: [Function] },
|
|
245
|
+
Teamdrives:
|
|
246
|
+
{ toString: [Function],
|
|
247
|
+
get: [Function],
|
|
248
|
+
create: [Function],
|
|
249
|
+
update: [Function],
|
|
250
|
+
list: [Function],
|
|
251
|
+
remove: [Function] } }
|
|
252
|
+
*/
|
|
@@ -92,9 +92,9 @@ if (typeof globalThis[name] === typeof undefined) {
|
|
|
92
92
|
// initializing auth etc
|
|
93
93
|
Syncit.fxInit()
|
|
94
94
|
|
|
95
|
-
console.log(`setting ${name} to global`)
|
|
96
95
|
const getApp = () => {
|
|
97
96
|
|
|
97
|
+
console.log(`setting ${name} to global`)
|
|
98
98
|
// if it hasn't been intialized yet then do that
|
|
99
99
|
if (!_app) {
|
|
100
100
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* fake Apps Script Session
|
|
4
|
+
* the idea here is to create a global entry for the singleton
|
|
5
|
+
* before we actually have everything we need to create it.
|
|
6
|
+
* We do this by using a proxy, intercepting calls to the
|
|
7
|
+
* initial sigleton and diverting them to a completed one
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Proxies } from '../../support/proxies.js'
|
|
11
|
+
import { newFakeSession } from './fakesession.js'
|
|
12
|
+
|
|
13
|
+
let _app = null
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* adds to global space to mimic Apps Script behavior
|
|
17
|
+
*/
|
|
18
|
+
const name = "Session"
|
|
19
|
+
if (typeof globalThis[name] === typeof undefined) {
|
|
20
|
+
|
|
21
|
+
const getApp = () => {
|
|
22
|
+
// if it hasn't been intialized yet then do that
|
|
23
|
+
if (!_app) {
|
|
24
|
+
console.log(`setting ${name} to global`)
|
|
25
|
+
_app = newFakeSession()
|
|
26
|
+
}
|
|
27
|
+
// this is the actual driveApp we'll return from the proxy
|
|
28
|
+
return _app
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Proxies.registerProxy(name, getApp)
|
|
32
|
+
|
|
33
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { newFakeUser} from './fakeuser.js'
|
|
2
|
+
import { Auth } from '../../support/auth.js'
|
|
3
|
+
import { Proxies } from '../../support/proxies.js'
|
|
4
|
+
|
|
5
|
+
class FakeSession {
|
|
6
|
+
constructor () {
|
|
7
|
+
this._activeUser = newFakeUser (Auth.getTokenInfo())
|
|
8
|
+
}
|
|
9
|
+
getActiveUser() {
|
|
10
|
+
return this._activeUser
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* there's no difference between active/effective on node
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
getEffectiveUser() {
|
|
17
|
+
return this.getActiveUser()
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
getActiveUserLocale() {
|
|
23
|
+
const lang = process.env.LANG || ''
|
|
24
|
+
// it'll be a format like en_US.UTF-8 so we need to drop the encoding to be like apps script
|
|
25
|
+
return lang.replace(/\..*/,'')
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* this'll come from the manifest on Node (on Apps Script it'll be where the user is running from)
|
|
29
|
+
* it's the same as the timezone
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
getScriptTimeZone() {
|
|
33
|
+
return Auth.getTimeZone()
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* this'll be an encrypted user ID - same as the one used for property/cache stores identification
|
|
37
|
+
* it's the same as the timezone
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
getTemporaryActiveUserKey() {
|
|
41
|
+
return Auth.getHashedUserId()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const newFakeSession = (...args) => Proxies.guard (new FakeSession(...args))
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* returned by Session.getActiveUser,getEffectiveUser()
|
|
5
|
+
* the only method documented nowadays is getEmail()
|
|
6
|
+
* @class FakeUser
|
|
7
|
+
*/
|
|
8
|
+
class FakeUser {
|
|
9
|
+
/**
|
|
10
|
+
* @param {object} p tokeninfo
|
|
11
|
+
* @param {string} p.email email
|
|
12
|
+
* @returns {FakeUser}
|
|
13
|
+
*/
|
|
14
|
+
constructor ({email}) {
|
|
15
|
+
this.__email = email
|
|
16
|
+
}
|
|
17
|
+
getEmail () {
|
|
18
|
+
return this.__email
|
|
19
|
+
}
|
|
20
|
+
toString () {
|
|
21
|
+
return this.getEmail()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* create a new FakeUser instance
|
|
27
|
+
* @param {...any} args
|
|
28
|
+
* @returns {FakeUser}
|
|
29
|
+
*/
|
|
30
|
+
export const newFakeUser = (...args) => {
|
|
31
|
+
return Proxies.guard(new FakeUser(...args))
|
|
32
|
+
}
|
|
@@ -17,13 +17,14 @@ let _app = null
|
|
|
17
17
|
const name = "SpreadsheetApp"
|
|
18
18
|
|
|
19
19
|
if (typeof globalThis[name] === typeof undefined) {
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
/**
|
|
22
22
|
* @returns {FakeSpreadsheetApp}
|
|
23
23
|
*/
|
|
24
24
|
const getApp = () => {
|
|
25
25
|
// if it hasnt been intialized yet then do that
|
|
26
26
|
if (!_app) {
|
|
27
|
+
console.log (`setting ${name} to global`)
|
|
27
28
|
_app = newFakeSpreadsheetApp()
|
|
28
29
|
}
|
|
29
30
|
// this is the actual driveApp we'll return from the proxy
|
|
@@ -21,11 +21,12 @@ let _cacheApp = null
|
|
|
21
21
|
*/
|
|
22
22
|
const registerApp = (_app, name, kind) => {
|
|
23
23
|
if (typeof globalThis[name] === typeof undefined) {
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
|
|
26
26
|
const getApp = () => {
|
|
27
27
|
// if it hasnt been intialized yet then do that
|
|
28
28
|
if (!_app) {
|
|
29
|
+
console.log(`setting ${name} to global`)
|
|
29
30
|
_app = newFakeService(kind)
|
|
30
31
|
}
|
|
31
32
|
// this is the actual driveApp we'll return from the proxy
|
|
@@ -160,9 +160,9 @@ class FakeStore {
|
|
|
160
160
|
const scriptId = Auth.getScriptId()
|
|
161
161
|
const documentId = Auth.getDocumentId()
|
|
162
162
|
|
|
163
|
-
let fileName = `${k}
|
|
163
|
+
let fileName = `${k}${t}-${scriptId}`
|
|
164
164
|
if (this.type === StoreType.USER) {
|
|
165
|
-
fileName += `-${Auth.
|
|
165
|
+
fileName += `-${Auth.getHashedUserId()}`
|
|
166
166
|
}
|
|
167
167
|
else if (this.type === StoreType.DOCUMENT && documentId) {
|
|
168
168
|
fileName += `-${documentId}`
|
|
@@ -69,10 +69,11 @@ let _app = null
|
|
|
69
69
|
*/
|
|
70
70
|
const name = "UrlFetchApp"
|
|
71
71
|
if (typeof globalThis[name] === typeof undefined) {
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
const getApp = () => {
|
|
74
74
|
// if it hasne been intialized yet then do that
|
|
75
75
|
if (!_app) {
|
|
76
|
+
console.log (`setting ${name} to global`)
|
|
76
77
|
_app = {
|
|
77
78
|
fetch
|
|
78
79
|
}
|
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
import sleepSynchronously from 'sleep-synchronously';
|
|
2
1
|
import { Proxies } from '../../support/proxies.js'
|
|
3
|
-
import {
|
|
4
|
-
import {Utils} from '../../support/utils.js'
|
|
5
|
-
/**
|
|
6
|
-
* a blocking sleep to emulate Apps Script
|
|
7
|
-
* @param {number} ms number of milliseconds to sleep
|
|
8
|
-
*/
|
|
9
|
-
const sleep = (ms) => {
|
|
10
|
-
Utils.assert.number (ms, `Cannot convert ${ms} to int.`)
|
|
11
|
-
sleepSynchronously(ms);
|
|
12
|
-
}
|
|
2
|
+
import { newFakeUtilities } from './fakeutilities.js';
|
|
13
3
|
|
|
14
4
|
|
|
15
|
-
// This will eventually hold a proxy for
|
|
5
|
+
// This will eventually hold a proxy for Utilities
|
|
16
6
|
let _app = null
|
|
17
7
|
|
|
18
8
|
/**
|
|
@@ -20,19 +10,14 @@ let _app = null
|
|
|
20
10
|
*/
|
|
21
11
|
const name = "Utilities"
|
|
22
12
|
if (typeof globalThis[name] === typeof undefined) {
|
|
23
|
-
console.log (`setting ${name} to global`)
|
|
24
13
|
const getApp = () => {
|
|
25
|
-
// if it
|
|
14
|
+
// if it hasnt been intialized yet then do that
|
|
26
15
|
if (!_app) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
newBlob
|
|
30
|
-
}
|
|
16
|
+
console.log (`setting ${name} to global`)
|
|
17
|
+
_app = newFakeUtilities()
|
|
31
18
|
}
|
|
32
19
|
// this is the actual driveApp we'll return from the proxy
|
|
33
20
|
return _app
|
|
34
21
|
}
|
|
35
|
-
|
|
36
22
|
Proxies.registerProxy (name, getApp)
|
|
37
|
-
|
|
38
23
|
}
|
|
@@ -11,10 +11,10 @@ class FakeBlob {
|
|
|
11
11
|
/**
|
|
12
12
|
*
|
|
13
13
|
* @constructor
|
|
14
|
-
* @param {
|
|
14
|
+
* @param {*} [data] data
|
|
15
15
|
* @param {string} [contentType]
|
|
16
16
|
* @param {string} [name]
|
|
17
|
-
* @returns {
|
|
17
|
+
* @returns {FakeBlob}
|
|
18
18
|
*/
|
|
19
19
|
constructor(data, contentType, name) {
|
|
20
20
|
this._data = Utils.settleAsBytes(data)
|
|
@@ -45,7 +45,7 @@ class FakeBlob {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
copyBlob() {
|
|
48
|
-
return
|
|
48
|
+
return newFakeBlob(this.getBytes(), this.getContentType(), this.getName())
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
setBytes(data) {
|
|
@@ -72,4 +72,4 @@ class FakeBlob {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
}
|
|
75
|
-
export const
|
|
75
|
+
export const newFakeBlob = (...args) => Proxies.guard(new FakeBlob(...args))
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import sleepSynchronously from 'sleep-synchronously';
|
|
2
|
+
import { Proxies } from '../../support/proxies.js'
|
|
3
|
+
import { newFakeBlob } from './fakeblob.js'
|
|
4
|
+
import { Utils } from '../../support/utils.js'
|
|
5
|
+
import { gzipType, zipType } from '../../support/constants.js'
|
|
6
|
+
import { randomUUID } from 'node:crypto'
|
|
7
|
+
import { gzipSync , gunzipSync} from 'node:zlib'
|
|
8
|
+
import { Syncit } from '../../support/syncit.js';
|
|
9
|
+
|
|
10
|
+
class FakeUtilities {
|
|
11
|
+
constructor() {
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* a blocking sleep to emulate Apps Script
|
|
16
|
+
* @param {number} ms number of milliseconds to sleep
|
|
17
|
+
*/
|
|
18
|
+
sleep(ms) {
|
|
19
|
+
Utils.assert.number(ms, `Cannot convert ${ms} to int.`)
|
|
20
|
+
sleepSynchronously(ms);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* @param {*} [data] data
|
|
25
|
+
* @param {string} [contentType]
|
|
26
|
+
* @param {string} [name]
|
|
27
|
+
* @returns {FakeBlob}
|
|
28
|
+
*/
|
|
29
|
+
newBlob(data, contentType, name) {
|
|
30
|
+
return newFakeBlob(data, contentType, name)
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* gets a uid
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
getUuid() {
|
|
37
|
+
return randomUUID()
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* gzip-compresses the provided Blob data and returns it in a new Blob object.
|
|
41
|
+
* @param {FakeBlob} blob
|
|
42
|
+
* @param {string} [name]
|
|
43
|
+
* @returns {FakeBlob}
|
|
44
|
+
*/
|
|
45
|
+
gzip(blob, name) {
|
|
46
|
+
// can set the name if required
|
|
47
|
+
const buffer = Buffer.from (blob.getBytes())
|
|
48
|
+
return this.newBlob (gzipSync(buffer), gzipType, (name || blob.getName() || 'archive.gz'))
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new Blob object that is a zip file containing the data from the Blobs passed in.
|
|
52
|
+
* @param {FakeBlob[]} blobs
|
|
53
|
+
* @param {string} [name=archive.zip]
|
|
54
|
+
* @returns {FakeBlob}
|
|
55
|
+
*/
|
|
56
|
+
zip(blobs, name = "archive.zip") {
|
|
57
|
+
// decided to use 'archiver' rather than zlib for this as the objective may be to create a file containing multiple files
|
|
58
|
+
// zlib only supports singe files
|
|
59
|
+
const zipped = Syncit.fxZipper ({blobs})
|
|
60
|
+
return newFakeBlob (zipped, zipType , name)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Takes a Blob representing a zip file and returns its component blobs.
|
|
65
|
+
* @param {FakeBlob} blob
|
|
66
|
+
* @param {string} [name]
|
|
67
|
+
* @returns {FakeBlob[]}
|
|
68
|
+
*/
|
|
69
|
+
unzip (blob) {
|
|
70
|
+
const unzipped = Syncit.fxUnZipper ({blob})
|
|
71
|
+
// the content type is lost in a zipped file, same as Apps Script behavior - which seems to be to use the extension to reassert content type
|
|
72
|
+
return unzipped.map (f=> newFakeBlob (f.bytes, null, f.name)).map(f=>f.setContentTypeFromExtension())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Uncompresses a Blob object and returns a Blob containing the uncompressed data.
|
|
77
|
+
* @param {FakeBlob} blob
|
|
78
|
+
* @returns {FakeBlob}
|
|
79
|
+
*/
|
|
80
|
+
ungzip(blob) {
|
|
81
|
+
const buffer = Buffer.from (blob.getBytes())
|
|
82
|
+
const name = blob.getName()
|
|
83
|
+
const newName = name ? name.replace(/\.gz$/,"") : null
|
|
84
|
+
return this.newBlob (gunzipSync(buffer), null, newName)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
base64Encode (data, charset) {
|
|
88
|
+
return Buffer.from(Utils.settleAsBytes(data,charset)).toString('base64')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
base64EncodeWebSafe (data, charset) {
|
|
92
|
+
return Buffer.from(Utils.settleAsBytes(data,charset)).toString('base64url')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
base64Decode (b64) {
|
|
96
|
+
return Utils.settleAsBytes (Buffer.from (b64, 'base64'))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
base64DecodeWebSafe (b64) {
|
|
100
|
+
return Utils.settleAsBytes (Buffer.from (b64, 'base64url'))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const newFakeUtilities = () => Proxies.guard(new FakeUtilities())
|
package/src/support/auth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GoogleAuth } from 'google-auth-library'
|
|
2
2
|
import is from '@sindresorhus/is'
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
3
4
|
|
|
4
5
|
const _authScopes = new Set([])
|
|
5
6
|
|
|
@@ -8,8 +9,14 @@ let _auth = null
|
|
|
8
9
|
let _projectId = null
|
|
9
10
|
let _tokenInfo = null
|
|
10
11
|
let _accessToken = null
|
|
12
|
+
let _manifest = null
|
|
13
|
+
let _clasp = null
|
|
11
14
|
|
|
12
15
|
let _settings = null
|
|
16
|
+
const setManifest = (manifest) => _manifest = manifest
|
|
17
|
+
const setClasp = (clasp) => _clasp = clasp
|
|
18
|
+
const getManifest = () => _manifest
|
|
19
|
+
const getClasp = () => _clasp
|
|
13
20
|
const getSettings = () => _settings
|
|
14
21
|
const getScriptId = () => getSettings().scriptId
|
|
15
22
|
const getDocumentId = () => getSettings().documentId
|
|
@@ -29,10 +36,10 @@ const getAccessToken= () => {
|
|
|
29
36
|
return _accessToken
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
const getTimeZone = () => getManifest().timeZone
|
|
33
40
|
const getUserId = () => getTokenInfo().sub
|
|
34
41
|
const getTokenScopes = () => getTokenInfo().scope
|
|
35
|
-
|
|
42
|
+
const getHashedUserId = () => createHash('md5').update(getUserId()+'hud').digest().toString('hex')
|
|
36
43
|
|
|
37
44
|
|
|
38
45
|
/**
|
|
@@ -142,5 +149,12 @@ export const Auth = {
|
|
|
142
149
|
getDocumentId,
|
|
143
150
|
setSettings,
|
|
144
151
|
getCachePath,
|
|
145
|
-
getPropertiesPath
|
|
152
|
+
getPropertiesPath,
|
|
153
|
+
getTokenInfo,
|
|
154
|
+
getHashedUserId,
|
|
155
|
+
setManifest,
|
|
156
|
+
setClasp,
|
|
157
|
+
getManifest,
|
|
158
|
+
getClasp,
|
|
159
|
+
getTimeZone
|
|
146
160
|
}
|
package/src/support/constants.js
CHANGED
|
@@ -15,3 +15,8 @@ export const spreadsheetType = `${gooType}.spreadsheet`
|
|
|
15
15
|
|
|
16
16
|
export const isGoogleType = (mimeType) =>
|
|
17
17
|
mimeType && mimeType.substring(0,gooType.length) === gooType
|
|
18
|
+
|
|
19
|
+
export const gzipType = 'application/x-gzip'
|
|
20
|
+
export const zipType = 'application/zip'
|
|
21
|
+
|
|
22
|
+
export const notYetImplemented = `That is not yet implemented - watch https://github.com/brucemcpherson/gas-fakes for progress`
|
package/src/support/syncit.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import makeSynchronous from 'make-synchronous';
|
|
5
5
|
import path from 'path'
|
|
6
6
|
import { Auth } from "./auth.js"
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { randomUUID } from 'node:crypto'
|
|
8
|
+
import mime from 'mime';
|
|
9
9
|
|
|
10
10
|
const authPath = "../support/auth.js"
|
|
11
11
|
const drapisPath = "../services/drive/drapis.js"
|
|
@@ -31,37 +31,146 @@ const cacheDefaultPath = "/tmp/gas-fakes/cache"
|
|
|
31
31
|
*/
|
|
32
32
|
const getModulePath = (relTarget) => path.resolve(import.meta.dirname, relTarget)
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* zipper
|
|
36
|
+
* @param {object} p
|
|
37
|
+
* @param {FakeBlob} p.blobs an array of blobs to be zipped
|
|
38
|
+
* @returns {FakeBlob} a combined zip file
|
|
39
|
+
*/
|
|
40
|
+
const fxZipper = ({ blobs }) => {
|
|
41
|
+
|
|
42
|
+
const fx = makeSynchronous(async ({ blobsContent }) => {
|
|
43
|
+
|
|
44
|
+
const { default: archiver } = await import('archiver')
|
|
45
|
+
const { getStreamAsBuffer } = await import('get-stream')
|
|
46
|
+
const { PassThrough } = await import('node:stream')
|
|
47
|
+
|
|
48
|
+
const passthrough = new PassThrough()
|
|
49
|
+
|
|
50
|
+
// just use the default compression level
|
|
51
|
+
|
|
52
|
+
const archive = archiver.create('zip', {})
|
|
53
|
+
|
|
54
|
+
const doArchive = async () => {
|
|
55
|
+
|
|
56
|
+
// warning could be non destructive
|
|
57
|
+
archive.on("warning", function (err) {
|
|
58
|
+
if (err.code === "ENOENT") {
|
|
59
|
+
console.log("....warning on archiver", err)
|
|
60
|
+
} else {
|
|
61
|
+
// throw error
|
|
62
|
+
return Promise.reject(err)
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
archive.on("error", function (err) {
|
|
67
|
+
return Promise.reject(err)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const result = getStreamAsBuffer(archive.pipe(passthrough))
|
|
71
|
+
|
|
72
|
+
blobsContent.forEach(f => {
|
|
73
|
+
archive.append(Buffer.from(f.bytes), { name: f.name })
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
archive.finalize()
|
|
77
|
+
|
|
78
|
+
return result.then(buffer => Array.from(buffer))
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return doArchive()
|
|
83
|
+
.catch(err => {
|
|
84
|
+
console.log('...archiver failed with error', err)
|
|
85
|
+
return Promise.reject(err)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
const dupCheck = new Set()
|
|
92
|
+
const blobsContent = blobs.map((f,i) => {
|
|
93
|
+
const ext = mime.getExtension(f.getContentType())
|
|
94
|
+
const name = f.getName() || `Untitled${i+1}${ext ? "."+ext : ""}`
|
|
95
|
+
if (dupCheck.has (name)) {
|
|
96
|
+
throw new Error(`Duplicate filename ${name} not allowed in zip`)
|
|
97
|
+
}
|
|
98
|
+
dupCheck.add (name)
|
|
99
|
+
return {
|
|
100
|
+
name,
|
|
101
|
+
bytes: f.getBytes()
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
// check we don't have duplicate names
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
return fx({
|
|
108
|
+
blobsContent
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
}
|
|
34
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Unzipper
|
|
116
|
+
* @param {object} p
|
|
117
|
+
* @param {FakeBlob} p.blob the blob containing the zipped files
|
|
118
|
+
* @returns {FakeBlob[]} each of the files unzipped
|
|
119
|
+
*/
|
|
120
|
+
const fxUnZipper = ({ blob }) => {
|
|
121
|
+
|
|
122
|
+
const fx = makeSynchronous(async ({ blobContent }) => {
|
|
123
|
+
|
|
124
|
+
const { default: unzipper } = await import('unzipper')
|
|
125
|
+
const { getStreamAsBuffer } = await import('get-stream')
|
|
126
|
+
|
|
127
|
+
const buffer = Buffer.from(blobContent.bytes)
|
|
128
|
+
const unzipped = await unzipper.Open.buffer(buffer)
|
|
129
|
+
|
|
130
|
+
const result = await Promise.all(unzipped.files.map(async file => {
|
|
131
|
+
const bytes = await getStreamAsBuffer(file.stream())
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
bytes,
|
|
135
|
+
name: file.path
|
|
136
|
+
}
|
|
137
|
+
}))
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const blobContent = {
|
|
143
|
+
name: blob.getName(),
|
|
144
|
+
bytes: blob.getBytes()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
return fx({
|
|
149
|
+
blobContent
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
}
|
|
35
154
|
/**
|
|
36
155
|
* we dont want to generate a lot of async/sync calls so start by getting themanifest stuff out of the way
|
|
37
156
|
* @param {string} [manifestPath ='./appsscript.json']
|
|
38
157
|
*
|
|
39
158
|
*/
|
|
40
|
-
const fxInit = ({
|
|
41
|
-
manifestPath = manifestDefaultPath,
|
|
42
|
-
claspPath = claspDefaultPath,
|
|
159
|
+
const fxInit = ({
|
|
160
|
+
manifestPath = manifestDefaultPath,
|
|
161
|
+
claspPath = claspDefaultPath,
|
|
43
162
|
settingsPath = settingsDefaultPath,
|
|
44
163
|
cachePath = cacheDefaultPath,
|
|
45
164
|
propertiesPath = propertiesDefaultPath
|
|
46
165
|
} = {}) => {
|
|
47
166
|
|
|
48
|
-
const fx = makeSynchronous(async ({ manifestPath, authPath, claspPath, settingsPath, mainDir, cachePath, propertiesPath }) => {
|
|
167
|
+
const fx = makeSynchronous(async ({ manifestPath, authPath, claspPath, settingsPath, mainDir, cachePath, propertiesPath, fakeId }) => {
|
|
49
168
|
|
|
50
169
|
// get the settings and manifest
|
|
51
170
|
const path = await import('path')
|
|
52
171
|
const { readFile, writeFile, mkdir } = await import('node:fs/promises')
|
|
53
172
|
const { Auth } = await import(authPath)
|
|
54
173
|
|
|
55
|
-
/// make a fake scriptid
|
|
56
|
-
const makeFakeId = (length = 24) => {
|
|
57
|
-
const seed = new Date().getTime()
|
|
58
|
-
let t = ''
|
|
59
|
-
while (t.length < length) {
|
|
60
|
-
t += Math.trunc(seed * Math.random()).toString(36)
|
|
61
|
-
}
|
|
62
|
-
return t.substring(0, length)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
174
|
// get a file and parse if it exists
|
|
66
175
|
const getIfExists = async (file) => {
|
|
67
176
|
try {
|
|
@@ -94,7 +203,7 @@ const fxInit = ({
|
|
|
94
203
|
])
|
|
95
204
|
|
|
96
205
|
/// if we dont have a scriptId we need to check in clasp or make a fakeone
|
|
97
|
-
settings.scriptId = settings.scriptId || clasp.scriptId ||
|
|
206
|
+
settings.scriptId = settings.scriptId || clasp.scriptId || fakeId
|
|
98
207
|
|
|
99
208
|
// if we don't have a documentID, then see if this is a bound one
|
|
100
209
|
settings.documentId = settings.documentId || null
|
|
@@ -103,12 +212,12 @@ const fxInit = ({
|
|
|
103
212
|
settings.cache = settings.cache || cachePath
|
|
104
213
|
settings.properties = settings.properties || propertiesPath
|
|
105
214
|
|
|
106
|
-
console.log
|
|
107
|
-
console.log
|
|
215
|
+
console.log(`...cache will be in ${settings.cache}`)
|
|
216
|
+
console.log(`...properties will be in ${settings.properties}`)
|
|
108
217
|
|
|
109
218
|
// now update all that if anything has changed
|
|
110
|
-
const strSet = JSON.stringify(settings,null,2)
|
|
111
|
-
if (JSON.stringify(_settings,null, 2) !== strSet) {
|
|
219
|
+
const strSet = JSON.stringify(settings, null, 2)
|
|
220
|
+
if (JSON.stringify(_settings, null, 2) !== strSet) {
|
|
112
221
|
await mkdir(settingsDir, { recursive: true })
|
|
113
222
|
console.log('...writing to ', settingsFile)
|
|
114
223
|
writeFile(settingsFile, strSet, { flag: 'w' })
|
|
@@ -140,7 +249,9 @@ const fxInit = ({
|
|
|
140
249
|
projectId,
|
|
141
250
|
tokenInfo,
|
|
142
251
|
accessToken,
|
|
143
|
-
settings
|
|
252
|
+
settings,
|
|
253
|
+
manifest,
|
|
254
|
+
clasp
|
|
144
255
|
}
|
|
145
256
|
|
|
146
257
|
|
|
@@ -158,14 +269,17 @@ const fxInit = ({
|
|
|
158
269
|
authPath: getModulePath(authPath),
|
|
159
270
|
mainDir,
|
|
160
271
|
cachePath,
|
|
161
|
-
propertiesPath
|
|
272
|
+
propertiesPath,
|
|
273
|
+
fakeId: randomUUID()
|
|
162
274
|
})
|
|
163
275
|
const {
|
|
164
276
|
scopes,
|
|
165
277
|
projectId,
|
|
166
278
|
tokenInfo,
|
|
167
279
|
accessToken,
|
|
168
|
-
settings
|
|
280
|
+
settings,
|
|
281
|
+
manifest,
|
|
282
|
+
clasp
|
|
169
283
|
} = synced
|
|
170
284
|
|
|
171
285
|
// set these values from the subprocess for the main project
|
|
@@ -174,6 +288,8 @@ const fxInit = ({
|
|
|
174
288
|
Auth.setTokenInfo(tokenInfo)
|
|
175
289
|
Auth.setAccessToken(accessToken)
|
|
176
290
|
Auth.setSettings(settings)
|
|
291
|
+
Auth.setClasp(clasp)
|
|
292
|
+
Auth.setManifest(manifest)
|
|
177
293
|
return synced
|
|
178
294
|
|
|
179
295
|
}
|
|
@@ -363,5 +479,7 @@ export const Syncit = {
|
|
|
363
479
|
fxDrive,
|
|
364
480
|
fxDriveMedia,
|
|
365
481
|
fxInit,
|
|
366
|
-
fxStore
|
|
482
|
+
fxStore,
|
|
483
|
+
fxZipper,
|
|
484
|
+
fxUnZipper
|
|
367
485
|
}
|