@mcpher/gas-fakes 1.2.31 → 2.0.0
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/.gcloudignore +24 -0
- package/README.md +5 -5
- package/appsscript.json +101 -0
- package/exgcp.sh +4 -4
- package/package.json +5 -6
- package/src/cli/app.js +9 -1
- package/src/cli/executor.js +32 -2
- package/src/cli/lib-manager.js +0 -1
- package/src/cli/setup.js +330 -228
- package/src/cli/utils.js +1 -1
- package/src/services/advcalendar/clapis.js +4 -2
- package/src/services/advdocs/docapis.js +4 -3
- package/src/services/advdrive/drapis.js +8 -5
- package/src/services/advforms/formsapis.js +4 -3
- package/src/services/advgmail/gmailapis.js +4 -3
- package/src/services/advsheets/shapis.js +5 -10
- package/src/services/advslides/slapis.js +4 -3
- package/src/services/driveapp/fakedriveapp.js +10 -2
- package/src/services/logger/fakelogger.js +6 -3
- package/src/services/scriptapp/app.js +16 -12
- package/src/services/scriptapp/behavior.js +132 -107
- package/src/services/session/fakesession.js +24 -9
- package/src/services/slidesapp/fakepresentation.js +19 -8
- package/src/services/slidesapp/fakeslide.js +45 -20
- package/src/services/spreadsheetapp/fakesheet.js +9 -7
- package/src/services/stores/fakestores.js +20 -18
- package/src/services/stores/gasflex.js +13 -14
- package/src/services/urlfetchapp/app.js +3 -3
- package/src/support/auth.js +227 -55
- package/src/support/slogger.js +42 -0
- package/src/support/sxauth.js +42 -39
- package/src/support/sxcalendar.js +9 -43
- package/src/support/sxdocs.js +6 -42
- package/src/support/sxdrive.js +32 -73
- package/src/support/sxfetch.js +36 -30
- package/src/support/sxforms.js +9 -40
- package/src/support/sxgmail.js +9 -37
- package/src/support/sxretry.js +79 -0
- package/src/support/sxsheets.js +6 -37
- package/src/support/sxslides.js +5 -36
- package/src/support/sxtoken.js +15 -0
- package/src/support/syncit.js +27 -10
- package/src/support/utils.js +13 -1
- package/src/support/workersync/sxfunctions.js +2 -0
- package/src/support/workersync/synclogger.js +22 -5
- package/src/support/workersync/worker.js +8 -11
- package/testlib.sh +1 -1
- package/README.RU.md +0 -373
- package/env.setup.template +0 -16
- package/run.js +0 -35
- package/setup.js +0 -689
- package/src/Code.js +0 -3
- package/src/appsscript.json +0 -5
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { isQuiet } from '../slogger.js';
|
|
3
|
+
|
|
4
|
+
const isCloudRun = !!process.env.K_SERVICE;
|
|
5
|
+
const getTimestamp = () => new Date().toISOString();
|
|
6
|
+
|
|
7
|
+
const writeSyncLog = (stream, severity, message) => {
|
|
8
|
+
if (isCloudRun) {
|
|
9
|
+
const entry = {
|
|
10
|
+
severity: severity,
|
|
11
|
+
message: message,
|
|
12
|
+
timestamp: getTimestamp()
|
|
13
|
+
};
|
|
14
|
+
fs.writeSync(stream, JSON.stringify(entry) + '\n');
|
|
15
|
+
} else {
|
|
16
|
+
fs.writeSync(stream, `${getTimestamp()} ${message}\n`);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
3
20
|
export const syncLog = (message) => {
|
|
4
|
-
if (!isQuiet)
|
|
21
|
+
if (!isQuiet) writeSyncLog(1, 'DEFAULT', `[Worker] ${message}`);
|
|
5
22
|
};
|
|
6
23
|
|
|
7
24
|
export const syncWarn = (message) => {
|
|
8
|
-
|
|
25
|
+
writeSyncLog(1, 'WARNING', `[Worker Warn] ${message}`);
|
|
9
26
|
};
|
|
10
27
|
|
|
11
28
|
export const syncInfo = (message) => {
|
|
12
|
-
if (!isQuiet)
|
|
29
|
+
if (!isQuiet) writeSyncLog(1, 'INFO', `[Worker Info] ${message}`);
|
|
13
30
|
};
|
|
31
|
+
|
|
14
32
|
export const syncError = (message, error) => {
|
|
15
|
-
// Providing the error object is optional
|
|
16
33
|
const errorMessage = error ? `: ${error?.stack || error}` : '';
|
|
17
|
-
|
|
34
|
+
writeSyncLog(2, 'ERROR', `[Worker Error] ${message}${errorMessage}`);
|
|
18
35
|
};
|
|
@@ -45,7 +45,7 @@ async function writeResult(result) {
|
|
|
45
45
|
Atomics.store(control, CONTROL_INDICES.DATA_SIZE, encodedResult.length); // data size
|
|
46
46
|
Atomics.store(control, CONTROL_INDICES.IS_ERROR, 0); // success flag
|
|
47
47
|
Atomics.store(control, CONTROL_INDICES.RESULT_TYPE, 0); // result type: buffer
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -115,22 +115,19 @@ parentPort.on('message', async (task) => {
|
|
|
115
115
|
// sxInit is special: it creates the auth state and returns serializable info.
|
|
116
116
|
result = await asyncFn(...task.args);
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
Auth.
|
|
124
|
-
Auth.setSettings(result.settings);
|
|
125
|
-
Auth.setClasp(result.clasp);
|
|
126
|
-
Auth.setManifest(result.manifest);
|
|
118
|
+
// await Auth.setAuth(result.scopes);
|
|
119
|
+
//Auth.setSettings(result.settings);
|
|
120
|
+
//Auth.setClasp(result.clasp);
|
|
121
|
+
//Auth.setManifest(result.manifest);
|
|
122
|
+
//Auth.setActiveUser(result.activeUser);
|
|
123
|
+
//Auth.setEffectiveUser(result.effectiveUser);
|
|
127
124
|
|
|
128
125
|
} else {
|
|
129
126
|
// All other sx* functions receive the worker's Auth object as their first argument.
|
|
130
127
|
if (!Auth.getProjectId()) {
|
|
131
128
|
throw new Error('[Worker] Not initialized. fxInit must be called first.');
|
|
132
129
|
}
|
|
133
|
-
result = await asyncFn(Auth
|
|
130
|
+
result = await asyncFn(Auth, ...task.args);
|
|
134
131
|
|
|
135
132
|
}
|
|
136
133
|
|
package/testlib.sh
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
gas-fakes -l bmFiddler@13EWG4-lPrEf34itxQhAQ7b9JEbmCBfO8uE4Mhr99CHi3Pw65oxXtq-rU \
|
|
1
|
+
node ./gas-fakes.js -l bmFiddler@13EWG4-lPrEf34itxQhAQ7b9JEbmCBfO8uE4Mhr99CHi3Pw65oxXtq-rU \
|
|
2
2
|
-s "const sheet=SpreadsheetApp.openById('1h9IGIShgVBVUrUjjawk5MaCEQte_7t32XeEP1Z5jXKQ').getSheets()[0];const fiddler = new bmFiddler.Fiddler(sheet);console.log (fiddler.getData().slice(0, 5));"
|
package/README.RU.md
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
# Подтверждение концепции реализации среды Apps Script на Node
|
|
2
|
-
|
|
3
|
-
Я использую clasp/vscode для разработки приложений Google Apps Script (GAS), но при использовании нативных сервисов GAS возникает слишком много переходов туда-сюда к IDE GAS во время тестирования. Я поставил себе цель реализовать фейковую версию среды выполнения GAS на Node, чтобы хотябы сделать тесты локально.
|
|
4
|
-
|
|
5
|
-
Это всего лишь демонстрационная реализация, поэтому я реализовал очень ограниченное количество сервисов и методов, но все сложные части уже на месте, так что остается только много рутинной работы (к которой я с энтузиазмом приглашаю любых заинтересованных соавторов).
|
|
6
|
-
|
|
7
|
-
## Начало работы
|
|
8
|
-
|
|
9
|
-
You can get the package from npm
|
|
10
|
-
|
|
11
|
-
```sh
|
|
12
|
-
npm i @mcpher/gas-fakes
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Идея заключается в том, что вы можете запускать локально на Node сервисы GAS (пока только те, что реализованны), и он (Node) будет использовать различные API Google Workspace для эмуляции того, что произошло бы, если бы вы запустили то же самое в среде GAS.
|
|
16
|
-
|
|
17
|
-
### Облачный проект
|
|
18
|
-
|
|
19
|
-
У вас нет доступа к облачному проекту, поддерживаемому GAS, поэтому вам нужно создать проект GCP для использования локально. Чтобы продублировать управление OAuth, обрабатываемое GAS, мы будем использовать Application Default Credentials. В этом репозитории есть некоторые скрипты для настройки и тестирования этих данных. Как только вы настроите облачный проект, перейдите в папку `shells` и добавьте свой `project id` в `setaccount.sh` и
|
|
20
|
-
|
|
21
|
-
### Тестируйте
|
|
22
|
-
|
|
23
|
-
Рекомендую использовать тестовый проект, включенный в репозиторий, чтобы убедиться, что все настроено правильно. Он использует Fake DriveApp service для проверки Auth и т.д. Просто измените преднастройки на значения, присутствующие в вашем собственном Диске, затем `npm i && npm test`. Обратите внимание, что я использую [юнит-тестировщик](https://ramblings.mcpher.com/apps-script-test-runner-library-ported-to-node/), который работает как в GAS, так и в Node, поэтому те же самые тесты будут выполняться в обоих средах.
|
|
24
|
-
|
|
25
|
-
### Передача в GAS
|
|
26
|
-
|
|
27
|
-
Скрипт `togas.sh` переместит ваши файлы в gas - просто установите папки `SOURCE` и `TARGET` в скрипте. Убедитесь, что у вас есть манифест `appsscript.json` в папке `SOURCE`, поскольку **gas-fakes** читает его для обработки OAuth на Node.
|
|
28
|
-
|
|
29
|
-
Вы можете написать проект, который будет работать на Node и вызывать сервисы GAS, и он также будет работать в среде GAS без изменений кода, за исключением того, что на стороне Node у вас есть этот один импорт
|
|
30
|
-
|
|
31
|
-
```js
|
|
32
|
-
// all the fake services are here
|
|
33
|
-
import '@mcpher/gas-fakes/main.js'
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
`togas.sh` удалит `imports` и `exports` по пути к Apps Script, который их не поддерживает.
|
|
37
|
-
|
|
38
|
-
## Подход
|
|
39
|
-
|
|
40
|
-
Google не опубликовали детали о среде исполнения GAS (насколько мне известно). Мы знаем то, что они раньше работали на эмуляторе JavaScript под названием [Rhino](https://ramblings.mcpher.com/gassnippets2/what-javascript-engine-is-apps-script-running-on/), основанном на Java, но несколько лет назад перешели на среду исполнения V8. Помимо этого, мы не знаем почти ничего, кроме того, что они работают где-то на серверах Google.
|
|
41
|
-
|
|
42
|
-
Было 3 основные сложные проблемы, которые нужно было преодолеть, чтобы это заработало
|
|
43
|
-
|
|
44
|
-
- GAS полностью синхронный, тогда как замена вызовов API Workspace на Node асинхронная.
|
|
45
|
-
- GAS автоматически обрабатывает инициализацию OAuth из файла манифеста, тогда как нам нужна дополнительный код или альтернативные подходы на Node.
|
|
46
|
-
- Сервисные синглтоны (например, DriveApp) автоматически инициализируются и доступны в глобальном пространстве имен, тогда как в Node им нужна некоторая пост-AUTH инициализация, упорядочивание инициализации и экспозиция.
|
|
47
|
-
- Итераторы GAS не такие же, как стандартные итераторы, так как у них есть метод `hasNext()` и они не ведут себя одинаково.
|
|
48
|
-
|
|
49
|
-
Помимо этого, реализация - это просто много рутинной работы. Вот как я справился с этими 3 проблемами.
|
|
50
|
-
|
|
51
|
-
### Синхронность против Асинхронности
|
|
52
|
-
|
|
53
|
-
Хотя Apps Script поддерживает синтаксис async/await/promise, он работает в блокирующем режиме. Я действительно не хотел настаивать на асинхронном кодировании в коде, ориентированном на GAS, поэтому мне нужно было найти способ эмулировать то, что, вероятно, делает среда GAS.
|
|
54
|
-
|
|
55
|
-
Так как асинхронность является фундаментальной для Node, нет простого способа преобразовать асинхронность в синхронность. Однако существует такое понятие как *[child-process](https://nodejs.org/api/child_process.html#child-process)*, который вы можете запустить для выполнения вещей, и он имеет метод [`execSync`](https://nodejs.org/api/child_process.html#child_processexecsynccommand-options), который задерживает возврат из дочернего процесса до тех пор, пока очередь переданного обещания не будет полностью завершена. Таким образом, самым простым решением является запуск асинхронного метода в дочернем процессе, ожидание его завершения и возврат результатов синхронно. Я обнаружил, что [Sindre Sorhus](https://github.com/sindresorhus) использует этот подход с [make-synchronous](https://github.com/sindresorhus/make-synchronous), поэтому и я использую это.
|
|
56
|
-
|
|
57
|
-
Вот простой пример того, как получить информацию о токене доступа синхронно
|
|
58
|
-
|
|
59
|
-
```js
|
|
60
|
-
/**
|
|
61
|
-
* a sync version of token checking
|
|
62
|
-
* @param {string} token the token to check
|
|
63
|
-
* @returns {object} access token info
|
|
64
|
-
*/
|
|
65
|
-
const fxCheckToken = (accessToken) => {
|
|
66
|
-
|
|
67
|
-
// now turn all that into a synchronous function - it runs as a subprocess, so we need to start from scratch
|
|
68
|
-
const fx = makeSynchronous(async accessToken => {
|
|
69
|
-
const { default: got } = await import('got')
|
|
70
|
-
const tokenInfo = await got(`https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=${accessToken}`).json()
|
|
71
|
-
return tokenInfo
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const result = fx(accessToken)
|
|
75
|
-
return result
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### OAuth
|
|
80
|
-
|
|
81
|
-
Здесь мы имеем два компонента решения.
|
|
82
|
-
|
|
83
|
-
#### Приложение с параметрами доступа по умолчанию / Application default credentials (ADC)
|
|
84
|
-
|
|
85
|
-
Чтобы избежать большого количества специфичного для Node кода и учетных данных, но все же обрабатывать OAuth, я решил, что мы можем просто полагаться на ADC. Это проблема, о которой я уже писал здесь [Application Default Credentials with Google Cloud and Workspace APIs](https://ramblings.mcpher.com/application-default-credentials-with-google-cloud-and-workspace-apis/)
|
|
86
|
-
|
|
87
|
-
Для настройки этого установите ID вашего проекта GCP и дополнительные области, которые вам нужны, в `shells/setaccount.sh`. В этом примере я сохраняю обычные области ADC и добавляю дополнительную область для доступа к Drive
|
|
88
|
-
|
|
89
|
-
```sh
|
|
90
|
-
# project ID
|
|
91
|
-
P=YOUR_GCP_PROJECT_ID
|
|
92
|
-
|
|
93
|
-
# config to activate - multiple configs can each be named
|
|
94
|
-
# here we're working on the default project configuration
|
|
95
|
-
AC=default
|
|
96
|
-
|
|
97
|
-
# these are the ones it sets by default - take some of these out if you want to minimize access
|
|
98
|
-
DEFAULT_SCOPES="https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/drive,openid,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/sqlservice.login"
|
|
99
|
-
|
|
100
|
-
# these are the ones we want to add (note comma at beginning)
|
|
101
|
-
EXTRA_SCOPES=",https://www.googleapis.com/auth/drive"
|
|
102
|
-
|
|
103
|
-
.....etc
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
После настройки самого проекта вы можете выполнить скрипт, и это настроит ваш ADC, чтобы запускать любые службы, которым требуются области, которые вы добавили.
|
|
107
|
-
|
|
108
|
-
##### примечание
|
|
109
|
-
|
|
110
|
-
Хотя может быть искушение добавить `https://www.googleapis.com/auth/script.external_request`, это не обязательно для ADC и, на самом деле, вызовет ошибку. Конечно, вам понадобится это в вашем манифесте Apps script.
|
|
111
|
-
|
|
112
|
-
##### тестирование ADC
|
|
113
|
-
|
|
114
|
-
`shells/testtoken.sh` можно использовать для проверки, что вы можете сгенерировать токен с достаточной областью. В этом примере я проверяю, что могу получить доступ к файлу, которым владею. Измените `id` на один из ваших.
|
|
115
|
-
|
|
116
|
-
```sh
|
|
117
|
-
# check tokens have scopes required for DRIVE access
|
|
118
|
-
# set below to a fileid on drive you have access to
|
|
119
|
-
FILE_ID=SOME_FILE_ID
|
|
120
|
-
|
|
121
|
-
....etc
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Рекомендую сделать это, чтобы убедиться, что Auth работает нормально, прежде чем начинать кодировать свое приложение.
|
|
125
|
-
|
|
126
|
-
#### Файл манифеста
|
|
127
|
-
|
|
128
|
-
**gas-fakes** читает файл манифеста, чтобы узнать, какие области вам нужны в проекте, использует библиотеку Google Auth для попытки авторизации и имеет `ScriptApp.getOauthToken()`, чтобы возвращать достаточно специфицированный токен, как это делает среда GAS. Просто убедитесь, что у вас есть `appsscript.json` в той же папке, что и ваш основной скрипт.
|
|
129
|
-
|
|
130
|
-
### Глобальная инициализация
|
|
131
|
-
|
|
132
|
-
Это было немного проблематично для реализации последовательности инициализации, но я хотел убедиться, что любые GAS сервисы, которые имитируются, доступны и инициализированы на стороне Node, как и в GAS. На момент написания (подмножество методов) этих сервисов реализовано.
|
|
133
|
-
|
|
134
|
-
v1.0.0 подтверждение концепции для
|
|
135
|
-
|
|
136
|
-
- `DriveApp`
|
|
137
|
-
- `ScriptApp`
|
|
138
|
-
- `UrlFetchApp`
|
|
139
|
-
- `Utilities`
|
|
140
|
-
|
|
141
|
-
#### Прокси и globalThis
|
|
142
|
-
|
|
143
|
-
Каждый сервис имеет `FakeClass`, но мне нужно было, чтобы цикл Auth был инициирован и выполнен перед тем, как сделать их публичными. Использование прокси был самым простым подходом.
|
|
144
|
-
|
|
145
|
-
Вот код для `ScriptApp`
|
|
146
|
-
|
|
147
|
-
```js
|
|
148
|
-
/**
|
|
149
|
-
* adds to global space to mimic Apps Script behavior
|
|
150
|
-
*/
|
|
151
|
-
const name = "ScriptApp"
|
|
152
|
-
|
|
153
|
-
if (typeof globalThis[name] === typeof undefined) {
|
|
154
|
-
|
|
155
|
-
console.log ('setting script app to global')
|
|
156
|
-
|
|
157
|
-
const getApp = () => {
|
|
158
|
-
|
|
159
|
-
// if it hasn't been intialized yet then do that
|
|
160
|
-
if (!_app) {
|
|
161
|
-
|
|
162
|
-
// we also need to do the manifest scopes thing and the project id
|
|
163
|
-
const projectId = Syncit.fxGetProjectId()
|
|
164
|
-
const manifest = Syncit.fxGetManifest()
|
|
165
|
-
Auth.setProjectId (projectId)
|
|
166
|
-
Auth.setManifestScopes(manifest)
|
|
167
|
-
|
|
168
|
-
_app = {
|
|
169
|
-
getOAuthToken,
|
|
170
|
-
requireAllScopes,
|
|
171
|
-
requireScopes,
|
|
172
|
-
AuthMode: {
|
|
173
|
-
FULL: 'FULL'
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
// this is the actual driveApp we'll return from the proxy
|
|
180
|
-
return _app
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
Proxies.registerProxy(name, getApp)
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
Вот как регистрируются прокси
|
|
190
|
-
|
|
191
|
-
```js
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* diverts the property get to another object returned by the getApp function
|
|
195
|
-
* @param {function} a function to get the proxy object to substitutes
|
|
196
|
-
* @returns {function} a handler for a proxy
|
|
197
|
-
*/
|
|
198
|
-
const getAppHandler = (getApp) => {
|
|
199
|
-
return {
|
|
200
|
-
|
|
201
|
-
get(_, prop, receiver) {
|
|
202
|
-
// this will let the caller know we're not really running in Apps Script
|
|
203
|
-
return (prop === 'isFake') ? true : Reflect.get(getApp(), prop, receiver);
|
|
204
|
-
},
|
|
205
|
-
|
|
206
|
-
ownKeys(_) {
|
|
207
|
-
return Reflect.ownKeys(getApp())
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const registerProxy = (name, getApp) => {
|
|
213
|
-
const value = new Proxy({}, getAppHandler(getApp))
|
|
214
|
-
// add it to the global space to mimic what apps script does
|
|
215
|
-
Object.defineProperty(globalThis, name, {
|
|
216
|
-
value,
|
|
217
|
-
enumerable: true,
|
|
218
|
-
configurable: false,
|
|
219
|
-
writable: false,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Коротко говоря, сервис регистрируется как пустой объект, но при любой попытке получить доступ к нему фактически возвращает другой объект, который обрабатывает запрос. В примере `ScriptApp` является пустым объектом, но доступ к `ScriptApp.getOAuthToken()` возвращает объект ложный (Fake) `ScriptApp`, который был инициализирован.
|
|
225
|
-
|
|
226
|
-
Также есть тест, чтобы проверить, запущены ли вы в GAS или на Node - `ScriptApp.isFake`
|
|
227
|
-
|
|
228
|
-
### Итераторы
|
|
229
|
-
|
|
230
|
-
Итератор, созданный генератором, не имеет функции `hasNext()`, в то время как итераторы GAS имеют. Чтобы обойти это, мы можем создать обычный итератор Node, но ввести обертку, чтобы конструктор фактически получил первый элемент, а `next()` использовал значение, которое мы уже посмотрели. Вот обертка для преобразования итератора в стиль GAS
|
|
231
|
-
|
|
232
|
-
```js
|
|
233
|
-
import { Proxies } from './proxies.js'
|
|
234
|
-
/**
|
|
235
|
-
* this is a class to add a hasnext to a generator
|
|
236
|
-
* @class Peeker
|
|
237
|
-
*
|
|
238
|
-
*/
|
|
239
|
-
class Peeker {
|
|
240
|
-
/**
|
|
241
|
-
* @constructor
|
|
242
|
-
* @param {function} generator the generator function to add a hasNext() to
|
|
243
|
-
* @returns {Peeker}
|
|
244
|
-
*/
|
|
245
|
-
constructor(generator) {
|
|
246
|
-
this.generator = generator
|
|
247
|
-
// in order to be able to do a hasnext we have to actually get the value
|
|
248
|
-
// this is the next value stored
|
|
249
|
-
this.peeked = generator.next()
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* we see if there's a next if the peeked at is all over
|
|
254
|
-
* @returns {Boolean}
|
|
255
|
-
*/
|
|
256
|
-
hasNext () {
|
|
257
|
-
return !this.peeked.done
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* get the next value - actually its already got and storef in peeked
|
|
262
|
-
* @returns {object} {value, done}
|
|
263
|
-
*/
|
|
264
|
-
next () {
|
|
265
|
-
if (!this.hasNext()) {
|
|
266
|
-
// TODO find out what driveapp does
|
|
267
|
-
throw new Error ('iterator is exhausted - there is no more')
|
|
268
|
-
}
|
|
269
|
-
// instead of returning the next, we return the prepeeked next
|
|
270
|
-
const value = this.peeked.value
|
|
271
|
-
this.peeked = this.generator.next()
|
|
272
|
-
return value
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export const newPeeker = (...args) => Proxies.guard(new Peeker (...args))
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
И пример использования, создающий итератор родителей из файла API Drive
|
|
280
|
-
|
|
281
|
-
```js
|
|
282
|
-
/**
|
|
283
|
-
* this gets an intertor to fetch all the parents meta data
|
|
284
|
-
* @param {FakeDriveMeta} {file} the meta data
|
|
285
|
-
* @returns {object} {Peeker}
|
|
286
|
-
*/
|
|
287
|
-
const getParentsIterator = ({
|
|
288
|
-
file
|
|
289
|
-
}) => {
|
|
290
|
-
|
|
291
|
-
Utils.assert.object(file)
|
|
292
|
-
Utils.assert.array(file.parents)
|
|
293
|
-
|
|
294
|
-
function* filesink() {
|
|
295
|
-
// the result tank, we just get them all by id
|
|
296
|
-
let tank = file.parents.map(id => getFileById({ id, allow404: false }))
|
|
297
|
-
|
|
298
|
-
while (tank.length) {
|
|
299
|
-
yield newFakeDriveFolder(tank.splice(0, 1)[0])
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// create the iterator
|
|
304
|
-
const parentsIt = filesink()
|
|
305
|
-
|
|
306
|
-
// a regular iterator doesnt support the same methods
|
|
307
|
-
// as Apps Script so we'll fake that too
|
|
308
|
-
return newPeeker(parentsIt)
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
## Помощь
|
|
314
|
-
|
|
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
|
-
- [this file](README.md)
|
|
332
|
-
- [named colors](named-colors.md)
|
|
333
|
-
- [sandbox](sandbox.md)
|
|
334
|
-
|
|
335
|
-
## <img src="./logo.png" alt="gas-fakes logo" width="50" align="top"> Further Reading
|
|
336
|
-
|
|
337
|
-
- [getting started](GETTING_STARTED.md) - how to handle authentication for restricted scopes.
|
|
338
|
-
- [readme](README.md)
|
|
339
|
-
- [initial idea and thoughts](https://ramblings.mcpher.com/a-proof-of-concept-implementation-of-apps-script-environment-on-node/)
|
|
340
|
-
- [Inside the volatile world of a Google Document](https://ramblings.mcpher.com/inside-the-volatile-world-of-a-google-document/)
|
|
341
|
-
- [Apps Script Services on Node – using apps script libraries](https://ramblings.mcpher.com/apps-script-services-on-node-using-apps-script-libraries/)
|
|
342
|
-
- [Apps Script environment on Node – more services](https://ramblings.mcpher.com/apps-script-environment-on-node-more-services/)
|
|
343
|
-
- [Turning async into synch on Node using workers](https://ramblings.mcpher.com/turning-async-into-synch-on-node-using-workers/)
|
|
344
|
-
- [All about Apps Script Enums and how to fake them](https://ramblings.mcpher.com/all-about-apps-script-enums-and-how-to-fake-them/)
|
|
345
|
-
- [Russian version](README.RU.md) ([credit Alex Ivanov](https://github.com/oshliaer)) - needs updating
|
|
346
|
-
- [colaborators](collaborators.md) - additional information for collaborators
|
|
347
|
-
- [oddities](oddities.md) - a collection of oddities uncovered during this project
|
|
348
|
-
- [gemini](gemini-observations.md) - some reflections and experiences on using gemini to help code large projects
|
|
349
|
-
- [named colors](named-colors.md)
|
|
350
|
-
- [sandbox](sandbox.md)
|
|
351
|
-
- [using apps script libraries with gas-fakes](libraries.md)
|
|
352
|
-
- [how libhandler works](libhandler.md)
|
|
353
|
-
- [article:using apps script libraries with gas-fakes](https://ramblings.mcpher.com/how-to-use-apps-script-libraries-directly-from-node/)
|
|
354
|
-
- [named range identity](named-range-identity.md)
|
|
355
|
-
- [adc and restricted scopes](https://ramblings.mcpher.com/how-to-allow-access-to-sensitive-scopes-with-application-default-credentials/)
|
|
356
|
-
- [push test pull](pull-test-push.md)
|
|
357
|
-
- [gas fakes cli](gas-fakes-cli.md)
|
|
358
|
-
- [sharing cache and properties between gas-fakes and live apps script](https://ramblings.mcpher.com/sharing-cache-and-properties-between-gas-fakes-and-live-apps-script/)
|
|
359
|
-
- [gas-fakes-cli now has built in mcp server and gemini extension](https://ramblings.mcpher.com/gas-fakes-cli-now-has-built-in-mcp-server-and-gemini-extension/)
|
|
360
|
-
- [gas-fakes CLI: Run apps script code directly from your terminal](https://ramblings.mcpher.com/gas-fakes-cli-run-apps-script-code-directly-from-your-terminal/)
|
|
361
|
-
- [How to allow access to sensitive scopes with Application Default Credentials](https://ramblings.mcpher.com/how-to-allow-access-to-sensitive-scopes-with-application-default-credentials/)
|
|
362
|
-
- [Supercharge Your Google Apps Script Caching with GasFlexCache](https://ramblings.mcpher.com/supercharge-your-google-apps-script-caching-with-gasflexcache/)
|
|
363
|
-
- [Fake-Sandbox for Google Apps Script: Granular controls.](https://ramblings.mcpher.com/fake-sandbox-for-google-apps-script-granular-controls/)
|
|
364
|
-
- [A Fake-Sandbox for Google Apps Script: Securely Executing Code Generated by Gemini CLI](https://ramblings.mcpher.com/gas-fakes-sandbox/)
|
|
365
|
-
- [Power of Google Apps Script: Building MCP Server Tools for Gemini CLI and Google Antigravity in Google Workspace Automation](https://medium.com/google-cloud/power-of-google-apps-script-building-mcp-server-tools-for-gemini-cli-and-google-antigravity-in-71e754e4b740)
|
|
366
|
-
- [A New Era for Google Apps Script: Unlocking the Future of Google Workspace Automation with Natural Language](https://medium.com/google-cloud/a-new-era-for-google-apps-script-unlocking-the-future-of-google-workspace-automation-with-natural-a9cecf87b4c6)
|
|
367
|
-
- [Next-Generation Google Apps Script Development: Leveraging Antigravity and Gemini 3.0](https://medium.com/google-cloud/next-generation-google-apps-script-development-leveraging-antigravity-and-gemini-3-0-c4d5affbc1a8)
|
|
368
|
-
- [Modern Google Apps Script Workflow Building on the Cloud](https://medium.com/google-cloud/modern-google-apps-script-workflow-building-on-the-cloud-2255dbd32ac3)
|
|
369
|
-
- [Bridging the Gap: Seamless Integration for Local Google Apps Script Development](https://medium.com/@tanaike/bridging-the-gap-seamless-integration-for-local-google-apps-script-development-9b9b973aeb02)
|
|
370
|
-
- [Next-Level Google Apps Script Development](https://medium.com/google-cloud/next-level-google-apps-script-development-654be5153912)
|
|
371
|
-
- [Secure and Streamlined Google Apps Script Development with gas-fakes CLI and Gemini CLI Extension](https://medium.com/google-cloud/secure-and-streamlined-google-apps-script-development-with-gas-fakes-cli-and-gemini-cli-extension-67bbce80e2c8)
|
|
372
|
-
- [Secure and Conversational Google Workspace Automation: Integrating Gemini CLI with a gas-fakes MCP Server](https://medium.com/google-cloud/secure-and-conversational-google-workspace-automation-integrating-gemini-cli-with-a-gas-fakes-mcp-0a5341559865)
|
|
373
|
-
- [A Fake-Sandbox for Google Apps Script: A Feasibility Study on Securely Executing Code Generated by Gemini CL](https://medium.com/google-cloud/a-fake-sandbox-for-google-apps-script-a-feasibility-study-on-securely-executing-code-generated-by-cc985ce5dae3)
|
package/env.setup.template
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# for testing authentication
|
|
2
|
-
GCP_PROJECT_ID="add your gcp project id here"
|
|
3
|
-
|
|
4
|
-
# this is optional
|
|
5
|
-
DRIVE_TEST_FILE_ID="add the id of some test file you have access to here"
|
|
6
|
-
|
|
7
|
-
# we'll use the default config for application default credentials
|
|
8
|
-
AC=default
|
|
9
|
-
# these are the scopes set by default - take some of these out if you want to minimize access
|
|
10
|
-
DEFAULT_SCOPES="https://www.googleapis.com/auth/userinfo.email,openid,https://www.googleapis.com/auth/cloud-platform"
|
|
11
|
-
EXTRA_SCOPES=",https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/spreadsheets,https://www.googleapis.com/auth/gmail.labels"
|
|
12
|
-
|
|
13
|
-
# if you need access to sensitive or restricted scopes you'll need to provide a credentials file
|
|
14
|
-
# this is a path relative to the project root, or an absolute path
|
|
15
|
-
CLIENT_CREDENTIAL_FILE="private/gasfakes-oa.json"
|
|
16
|
-
LOG_DESTINATION="CONSOLE"
|
package/run.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import vm from 'vm';
|
|
4
|
-
|
|
5
|
-
// Import gas-fakes to initialize the environment
|
|
6
|
-
import '@mcpher/gas-fakes';
|
|
7
|
-
|
|
8
|
-
// Create a sandboxed context
|
|
9
|
-
const context = vm.createContext({
|
|
10
|
-
ScriptApp: global.ScriptApp,
|
|
11
|
-
console: console,
|
|
12
|
-
// Add other GAS globals you need here (e.g., DocumentApp, SpreadsheetApp)
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
// Read the user's script file
|
|
16
|
-
const scriptPath = path.resolve('src/Code.js');
|
|
17
|
-
const scriptCode = fs.readFileSync(scriptPath, 'utf8');
|
|
18
|
-
|
|
19
|
-
// Run the user's script in the sandbox to define the functions
|
|
20
|
-
vm.runInContext(scriptCode, context);
|
|
21
|
-
|
|
22
|
-
// Define and run the calling logic to execute a function from the script
|
|
23
|
-
const callingCode = `
|
|
24
|
-
// 1. Enable sandbox mode for safety
|
|
25
|
-
ScriptApp.__behavior.sandBoxMode = true;
|
|
26
|
-
|
|
27
|
-
// 2. Call the specific function from your Code.js file
|
|
28
|
-
// myFunction();
|
|
29
|
-
|
|
30
|
-
// 3. Clean up the gas-fakes environment
|
|
31
|
-
ScriptApp.__behavior.trash();
|
|
32
|
-
`;
|
|
33
|
-
|
|
34
|
-
// vm.runInContext(callingCode, context);
|
|
35
|
-
console.log("Project initialized. Edit run.js to execute a function.");
|