@mcpher/gas-fakes 1.0.19 → 1.0.21

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.
Files changed (72) hide show
  1. package/README.md +63 -30
  2. package/gasmess/bruce/pbx.js +53 -2
  3. package/gprompts/gas-inventory.js +92 -0
  4. package/gprompts/gas-inventory.json +176 -0
  5. package/gprompts/inventory-list.json +1 -0
  6. package/gprompts/model.json +34 -0
  7. package/gprompts/package-lock.json +1103 -0
  8. package/gprompts/package.json +18 -0
  9. package/gprompts/temp_fetch.mjs +9 -0
  10. package/gprompts/update-progress.js +142 -0
  11. package/package.json +6 -2
  12. package/setup.sh +147 -0
  13. package/src/index.js +9 -2
  14. package/src/services/advdocs/app.js +4 -23
  15. package/src/services/advdrive/app.js +6 -28
  16. package/src/services/advforms/app.js +6 -25
  17. package/src/services/advgmail/app.js +11 -0
  18. package/src/services/advgmail/fakeadvgmail.js +39 -0
  19. package/src/services/advgmail/fakeadvgmaillabels.js +119 -0
  20. package/src/services/advgmail/fakeadvgmailusers.js +23 -0
  21. package/src/services/advgmail/gmailapis.js +15 -0
  22. package/src/services/advsheets/app.js +6 -26
  23. package/src/services/advslides/app.js +6 -28
  24. package/src/services/common/lazyloader.js +22 -0
  25. package/src/services/documentapp/app.js +8 -42
  26. package/src/services/documentapp/appenderhelpers.js +21 -23
  27. package/src/services/documentapp/elementhelpers.js +0 -1
  28. package/src/services/documentapp/elementoptions.js +22 -32
  29. package/src/services/documentapp/fakeelement.js +20 -3
  30. package/src/services/documentapp/fakelistitem.js +177 -28
  31. package/src/services/documentapp/fakeparagraph.js +194 -7
  32. package/src/services/documentapp/faketable.js +16 -0
  33. package/src/services/documentapp/faketablerow.js +15 -0
  34. package/src/services/documentapp/nrhelpers.js +1 -0
  35. package/src/services/documentapp/shadowdocument.js +10 -0
  36. package/src/services/driveapp/app.js +6 -28
  37. package/src/services/enums/gmailenums.js +8 -0
  38. package/src/services/formapp/app.js +5 -40
  39. package/src/services/gmailapp/app.js +11 -0
  40. package/src/services/gmailapp/fakegmailapp.js +35 -0
  41. package/src/services/gmailapp/fakegmaillabel.js +44 -0
  42. package/src/services/logger/app.js +8 -0
  43. package/src/services/logger/fakelogger.js +162 -0
  44. package/src/services/scriptapp/app.js +7 -1
  45. package/src/services/scriptapp/behavior.js +1 -1
  46. package/src/services/session/app.js +10 -0
  47. package/src/services/slidesapp/app.js +5 -40
  48. package/src/services/spreadsheetapp/app.js +6 -50
  49. package/src/services/spreadsheetapp/fakeprotection.js +6 -7
  50. package/src/services/spreadsheetapp/fakesheet.js +3 -4
  51. package/src/services/stores/app.js +0 -1
  52. package/src/services/urlfetchapp/app.js +0 -1
  53. package/src/services/utilities/app.js +6 -20
  54. package/src/support/gmailcacher.js +7 -0
  55. package/src/support/helpers.js +2 -2
  56. package/src/support/proxies.js +20 -1
  57. package/src/support/sxgmail.js +55 -0
  58. package/src/support/syncit.js +5 -2
  59. package/src/support/utils.js +46 -15
  60. package/src/support/workersync/sxfunctions.js +5 -10
  61. package/togas.bash +18 -5
  62. package/ghissues/image-size-inconsistency-issue.sh +0 -46
  63. package/ghissues/issue-formapp-create-title-inconsistency.sh +0 -51
  64. package/ghissues/issue-positioned-image.sh +0 -25
  65. package/ghissues/post-issue.sh +0 -53
  66. package/ghissues/protection-editors-issue.sh +0 -33
  67. package/ghissues/review-sandbox-listing-issue.sh +0 -45
  68. package/ghissues/sandbox-issue.sh +0 -31
  69. package/ghissues/setup-under-construction.sh +0 -107
  70. package/src/services/base/app.js +0 -33
  71. /package/{regenerate-progress-reports.sh → gprompts/regenerate-progress-reports.sh} +0 -0
  72. /package/src/services/{base → session}/fakesession.js +0 -0
package/README.md CHANGED
@@ -33,15 +33,19 @@ In order to duplicate the OAuth management handled by GAS, we'll use Application
33
33
 
34
34
  #### Application default credentials
35
35
 
36
- At the very least you need to add the gcp project id you'll be using for testing, plus the id of some file you have access to - this'll be used to check that you have set up ADC properly.
36
+ In order to avoid a bunch of Node specific code and credentials, yet still handle OAuth, I figured that we could simply rely on ADC. This is a problem I already wrote about here [Application Default Credentials with Google Cloud and Workspace APIs](https://ramblings.mcpher.com/application-default-credentials-with-google-cloud-and-workspace-apis/)
37
+
38
+ At the very least you need to add the gcp project id and optionally the id of some file you have access to - this'll be used to check that you have set up ADC properly.
37
39
 
38
- There are other things in the .env-template you can ignore unless you're planning to run the test suite. More information on that is in [collaborators info](collaborators.md)
40
+ #### Your .env file
39
41
 
42
+ You can setup the .env file your self using the .env.template as a guide.
40
43
 
41
- These should be in your .env file to enable ADC authentication. The purpose of the DRIVE_TEST_FILE_ID is so that the script can check you've enabled ADC correctly by pinging a file you have access to. The GCP_PROJECT_ID is required as it will be used by gas-fakes to access the workspace apis on your behalf.
42
44
  ```
43
45
  # must set these
44
46
  GCP_PROJECT_ID="add your gcp project id here"
47
+
48
+ # optional reference if you want to run a test after setting up
45
49
  DRIVE_TEST_FILE_ID="add the id of some test file you have access to here"
46
50
 
47
51
  # we'll use the default config for application default credentials
@@ -50,31 +54,13 @@ AC=default
50
54
  # these are the scopes set by default - take some of these out if you want to minimize access
51
55
  DEFAULT_SCOPES="https://www.googleapis.com/auth/userinfo.email,openid,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/sqlservice.login"
52
56
  EXTRA_SCOPES=",https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/spreadsheets"
57
+
58
+ # optional logging destination
59
+ # can be CONSOLE (default), CLOUD, BOTH, NONE
60
+ LOG_DESTINATION="BOTH"
53
61
 
54
62
  ```
55
63
 
56
- - goto ./shells and execute sp.sh
57
-
58
- ### OAuth
59
-
60
- There's 2 pieces to this solution.
61
-
62
- #### Application default credentials (ADC)
63
-
64
- In order to avoid a bunch of Node specific code and credentials, yet still handle OAuth, I figured that we could simply rely on ADC. This is a problem I already wrote about here [Application Default Credentials with Google Cloud and Workspace APIs](https://ramblings.mcpher.com/application-default-credentials-with-google-cloud-and-workspace-apis/)
65
-
66
- This section in your env file controls which scopes you plan to use.
67
-
68
- ```
69
- we'll use the default config for application default credentials
70
- AC=default
71
- # these are the scopes set by default - take some of these out if you want to minimize access
72
- DEFAULT_SCOPES="https://www.googleapis.com/auth/userinfo.email,openid,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/sqlservice.login"
73
- EXTRA_SCOPES=",https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/spreadsheets"
74
-
75
- .....etc
76
- ```
77
-
78
64
  #### Manifest file
79
65
 
80
66
  If you have an associated apps script project, you'll probably be using clasp to sync with the apps script IDE, and you'll have an appsscript.json available in your project folder
@@ -111,6 +97,53 @@ Optionally, gasfakes.json holds various location and behavior parameters to info
111
97
  | properties | string | /tmp/gas-fakes/properties | gas-fakes uses a local file to emulate apps script's PropertiesService. This is where it should put the files. You may want to put it somewhere other than /tmp to avoid accidental deletion, but don't put it in a place that'll get commited to public git repo |
112
98
  | scriptId | string | from clasp, or some random value | If you have a clasp file, it'll pick up the scriptId from there. If not you can enter your scriptId manually, or just leave it to create a fake one. It's use for the moment is to return something useful from ScriptApp.getScriptId() and to partition the cache and properties stores |
113
99
 
100
+
101
+
102
+ ### Cloud Logging Integration
103
+
104
+ `gas-fakes` emulates the native Google Apps Script `Logger.log()` integration with Google Cloud Logging, allowing you to send structured logs from your local Node.js environment directly to your Google Cloud project. Note that console.log is the normal Node console and writes to the local console only. All messages from gas-fakes api still go to the console, so the Logger.log is for your own user messages as required.
105
+
106
+ #### Initial Configuration
107
+
108
+ The initial logging behavior is controlled by the `LOG_DESTINATION` environment variable in your `.env` file.
109
+
110
+ | `LOG_DESTINATION` | Behavior |
111
+ |---|---|
112
+ | `CONSOLE` (Default) | Logs structured JSON to the console (`stdout`). This is the default behavior if the variable is not set. |
113
+ | `CLOUD` | Sends logs directly to Google Cloud Logging. Nothing is written to the console. |
114
+ | `BOTH` | Sends logs to both Google Cloud Logging and the console. |
115
+ | `NONE` | Disables all output from `Logger.log()`. |
116
+
117
+ When logging to the cloud, entries are sent to the `gas-fakes/console_logs` log name and include the following labels for easy filtering in the Log Explorer:
118
+ - `gas-fakes-scriptId`
119
+ - `gas-fakes-userId`
120
+
121
+ #### Dynamic Control
122
+
123
+ You can change the logging destination at any time during runtime using the `Logger.__setLogDestination()` method. This is especially useful for testing or for applications that need to change their logging behavior dynamically.
124
+
125
+ The method accepts one of the following string values: `'CONSOLE'`, `'CLOUD'`, `'BOTH'`, or `'NONE'`.
126
+
127
+ #### Example Usage
128
+
129
+ To set the initial destination, modify your `.env` file:
130
+ ```
131
+ LOG_DESTINATION="BOTH"
132
+ ```
133
+
134
+ To change the destination during runtime in your script:
135
+ ```javascript
136
+ // Initially logs to BOTH (from .env)
137
+ Logger.log('This goes to console and cloud');
138
+
139
+ // Switch to only logging to the console
140
+ Logger.__setLogDestination('CONSOLE');
141
+ Logger.log('This now only goes to the console');
142
+
143
+ // Disable logging completely
144
+ Logger.__setLogDestination('NONE');
145
+ Logger.log('This goes nowhere');
146
+ ```
114
147
  ### Pushing files to GAS
115
148
 
116
149
  There are a couple of syntactical differences between Node and Apps Script. Not in the body of the code but in how the IDE executes. The 2 main ones are
@@ -120,16 +153,15 @@ There are a couple of syntactical differences between Node and Apps Script. Not
120
153
  // this required on Node but not on Apps Script
121
154
  if (ScriptApp.isFake) testFakes()
122
155
  ````
123
- For inspiration on pushing modified files to the IDE, see the [bash script](https://github.com/brucemcpherson/gas-fakes/blob/main/togas.bash) I use for the test suite.
124
-
125
-
156
+ For inspiration on pushing modified files to the IDE, see the togas.sh bash script I use for the test suite.
126
157
 
127
158
  ## Help
128
159
 
129
- As I mentioned earlier, to take this further, I'm going to need a lot of help to extend the methods and services supported - so if you feel this would be useful to you, and would like to collaborate, please ping me on [bruce@mcpher.com](mailto:bruce@mcpher.com) and we'll talk.
160
+ As I mentioned earlier, to take this further, I'm going to need a lot of help to extend the methods and services supported - so if you feel this would be useful to you, and would like to collaborate, please ping me on bruce@mcpher.com and we'll talk.
130
161
 
131
162
  ## Translations and writeups
132
163
 
164
+
133
165
  - [initial idea and thoughts](https://ramblings.mcpher.com/a-proof-of-concept-implementation-of-apps-script-environment-on-node/)
134
166
  - [Inside the volatile world of a Google Document](https://ramblings.mcpher.com/inside-the-volatile-world-of-a-google-document/
135
167
  - [Apps Script Services on Node – using apps script libraries](https://ramblings.mcpher.com/apps-script-services-on-node-using-apps-script-libraries/)
@@ -144,4 +176,5 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
144
176
  - [setup env](setup-env.md) - ([credit Eric Shapiro] - additional info on contents of .env file
145
177
  - [this file](README.md)
146
178
  - [named colors](named-colors.md)
147
- - [sandbox](sandbox.md)
179
+ - [sandbox](sandbox.md)
180
+ - [named range identity](named-range-identity.md)
@@ -5,6 +5,58 @@ import { report, scl } from './dreport.js';
5
5
 
6
6
 
7
7
  const suffix = "-bruce"
8
+ const tli = () => {
9
+ let doc = DocumentApp.create("abc")
10
+ const id = doc.getId()
11
+ moveToTempFolder(id, suffix)
12
+ let body = doc.getBody();
13
+ let li = body.appendListItem("Initial list item text.");
14
+
15
+ doc = scl(doc)
16
+ let d = Docs.Documents.get(id)
17
+ console.log('after append item', doc.getBody().getNumChildren())
18
+ console.log(JSON.stringify(d.body.content))
19
+
20
+ body = doc.getBody()
21
+ li = body.getChild(1)
22
+ li.clear();
23
+
24
+ doc = scl(doc)
25
+ d = Docs.Documents.get(id)
26
+ console.log('after clear', doc.getBody().getNumChildren())
27
+ console.log(JSON.stringify(d.body.content))
28
+
29
+ body = doc.getBody()
30
+ li = body.getChild(1)
31
+ li.setText("New text after clear.");
32
+ doc = scl(doc)
33
+ d = Docs.Documents.get(id)
34
+ console.log('after new text', doc.getBody().getNumChildren())
35
+ console.log(JSON.stringify(d.body.content))
36
+
37
+ body = doc.getBody()
38
+ li = body.getChild(1)
39
+ li.setText("Image test: "); // setText returns void, so we can't chain.
40
+
41
+ doc = scl(doc)
42
+ d = Docs.Documents.get(id)
43
+ console.log('after image text', doc.getBody().getNumChildren())
44
+ console.log(JSON.stringify(d.body.content))
45
+
46
+
47
+ const imageUrl = 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png';
48
+ const imageBlob = UrlFetchApp.fetch(imageUrl).getBlob();
49
+ body = doc.getBody()
50
+ li = body.getChild(1)
51
+ li.appendInlineImage(imageBlob.copyBlob());
52
+
53
+ console.log('after inlinr image', doc.getBody().getNumChildren())
54
+ console.log(JSON.stringify(d.body.content))
55
+
56
+ deleteTempFile(id)
57
+
58
+ }
59
+ tli()
8
60
 
9
61
  const tx2 = () => {
10
62
  let doc = DocumentApp.create("abc")
@@ -34,7 +86,6 @@ const tx2 = () => {
34
86
  deleteTempFile(id)
35
87
  }
36
88
 
37
- tx2()
38
89
  const tx1 = () => {
39
90
  let doc = DocumentApp.create("abc")
40
91
  const id = doc.getId()
@@ -53,7 +104,7 @@ const tx1 = () => {
53
104
 
54
105
  deleteTempFile(id)
55
106
  }
56
- tx1()
107
+
57
108
  const tabsa = () => {
58
109
 
59
110
  let doc = DocumentApp.create("abc")
@@ -0,0 +1,92 @@
1
+ import got from 'got';
2
+ import * as cheerio from 'cheerio';
3
+ import fs from 'fs/promises';
4
+ import { URL } from 'url';
5
+
6
+ const baseUrl = "https://developers.google.com/apps-script/reference/"
7
+ const outputFile = './gas-inventory.json';
8
+
9
+ const visited = new Map();
10
+ const queue = [baseUrl]
11
+
12
+ const kebabCamel = (s) => {
13
+ return s.replace(/([-][a-z])/ig, ($1) => {
14
+ return $1.toUpperCase()
15
+ .replace('-', '')
16
+ });
17
+ };
18
+ async function scrape() {
19
+ const inventory = {};
20
+
21
+ while (queue.length > 0) {
22
+ const url = queue.shift();
23
+ console.log(`Scraping ${url}`);
24
+ const response = await got(url);
25
+ const $ = cheerio.load(response.body);
26
+ const serviceName = $('h1').text().split('\n').map(f=>f.replace(/^\s+/,'').trim()).filter (f=>f)[0];
27
+ console.log ('...starting service name',serviceName)
28
+ inventory[serviceName] = {
29
+ name: serviceName,
30
+ link: url,
31
+ classes: {},
32
+ };
33
+
34
+ $('a').each((i, el) => {
35
+ const href = $(el).attr('href');
36
+ if (href && href.startsWith('/apps-script/reference')) {
37
+ const absoluteUrl = new URL(href, url).href;
38
+ const parentUrl = absoluteUrl.replace (/#.*/, '')
39
+ const method = kebabCamel(absoluteUrl.match(/#(.*)/) || ['', ''][1])
40
+ if (!visited.has(parentUrl)) {
41
+ console.log ('...adding to queue',parentUrl)
42
+ queue.push(parentUrl);
43
+ visited.set(parentUrl, {
44
+ url: parentUrl,
45
+ methods: new Map ()
46
+ });
47
+ }
48
+ if (method) {
49
+ visited.get(parentUrl).methods.set(absoluteUrl, {
50
+ url: absoluteUrl,
51
+ method
52
+ })
53
+ }
54
+ }
55
+ });
56
+
57
+ // Extract classes and methods
58
+ $('h2').each((i, el) => {
59
+ const className = $(el).text().trim();
60
+ if (className) {
61
+ console.log ('...found class',className,'for service',serviceName)
62
+ inventory[serviceName].classes[className] = {
63
+ methods: {},
64
+ properties: {},
65
+ };
66
+
67
+ // Find the table of methods for the current class
68
+ const methodsTable = $(el).nextAll('table').first();
69
+ methodsTable.find('tbody tr').each((j, row) => {
70
+ const methodName = $(row).find('td').eq(1).text().trim();
71
+ const methodLink = $(row).find('td').eq(1).find('a').attr('href');
72
+ const returnType = $(row).find('td').eq(0).text().trim();
73
+
74
+ if (methodName) {
75
+ inventory[serviceName].classes[className].methods[methodName] = {
76
+ link: methodLink ? new URL(methodLink, url).href : '',
77
+ returns: {
78
+ type: returnType,
79
+ link: '' // to be extracted later
80
+ }
81
+ };
82
+ }
83
+ });
84
+ }
85
+ });
86
+ }
87
+
88
+ await fs.writeFile(outputFile, JSON.stringify(inventory, null, 2));
89
+ console.log(`Inventory saved to ${outputFile}`);
90
+ }
91
+
92
+ scrape().catch(console.error);
@@ -0,0 +1,176 @@
1
+ {
2
+ "Forms Service": {
3
+ "name": "Forms Service",
4
+ "link": "https://developers.google.com/apps-script/reference/forms",
5
+ "classes": {
6
+ "Classes": {
7
+ "methods": {},
8
+ "properties": {}
9
+ },
10
+ "Alignment": {
11
+ "methods": {},
12
+ "properties": {}
13
+ },
14
+ "CheckboxGridItem": {
15
+ "methods": {},
16
+ "properties": {}
17
+ },
18
+ "CheckboxGridValidation": {
19
+ "methods": {},
20
+ "properties": {}
21
+ },
22
+ "CheckboxGridValidationBuilder": {
23
+ "methods": {},
24
+ "properties": {}
25
+ },
26
+ "CheckboxItem": {
27
+ "methods": {},
28
+ "properties": {}
29
+ },
30
+ "CheckboxValidation": {
31
+ "methods": {},
32
+ "properties": {}
33
+ },
34
+ "CheckboxValidationBuilder": {
35
+ "methods": {},
36
+ "properties": {}
37
+ },
38
+ "Choice": {
39
+ "methods": {},
40
+ "properties": {}
41
+ },
42
+ "DateItem": {
43
+ "methods": {},
44
+ "properties": {}
45
+ },
46
+ "DateTimeItem": {
47
+ "methods": {},
48
+ "properties": {}
49
+ },
50
+ "DestinationType": {
51
+ "methods": {},
52
+ "properties": {}
53
+ },
54
+ "DurationItem": {
55
+ "methods": {},
56
+ "properties": {}
57
+ },
58
+ "FeedbackType": {
59
+ "methods": {},
60
+ "properties": {}
61
+ },
62
+ "Form": {
63
+ "methods": {},
64
+ "properties": {}
65
+ },
66
+ "FormApp": {
67
+ "methods": {},
68
+ "properties": {}
69
+ },
70
+ "FormResponse": {
71
+ "methods": {},
72
+ "properties": {}
73
+ },
74
+ "GridItem": {
75
+ "methods": {},
76
+ "properties": {}
77
+ },
78
+ "GridValidation": {
79
+ "methods": {},
80
+ "properties": {}
81
+ },
82
+ "GridValidationBuilder": {
83
+ "methods": {},
84
+ "properties": {}
85
+ },
86
+ "ImageItem": {
87
+ "methods": {},
88
+ "properties": {}
89
+ },
90
+ "Item": {
91
+ "methods": {},
92
+ "properties": {}
93
+ },
94
+ "ItemResponse": {
95
+ "methods": {},
96
+ "properties": {}
97
+ },
98
+ "ItemType": {
99
+ "methods": {},
100
+ "properties": {}
101
+ },
102
+ "ListItem": {
103
+ "methods": {},
104
+ "properties": {}
105
+ },
106
+ "MultipleChoiceItem": {
107
+ "methods": {},
108
+ "properties": {}
109
+ },
110
+ "PageBreakItem": {
111
+ "methods": {},
112
+ "properties": {}
113
+ },
114
+ "PageNavigationType": {
115
+ "methods": {},
116
+ "properties": {}
117
+ },
118
+ "ParagraphTextItem": {
119
+ "methods": {},
120
+ "properties": {}
121
+ },
122
+ "ParagraphTextValidation": {
123
+ "methods": {},
124
+ "properties": {}
125
+ },
126
+ "ParagraphTextValidationBuilder": {
127
+ "methods": {},
128
+ "properties": {}
129
+ },
130
+ "QuizFeedback": {
131
+ "methods": {},
132
+ "properties": {}
133
+ },
134
+ "QuizFeedbackBuilder": {
135
+ "methods": {},
136
+ "properties": {}
137
+ },
138
+ "RatingIconType": {
139
+ "methods": {},
140
+ "properties": {}
141
+ },
142
+ "RatingItem": {
143
+ "methods": {},
144
+ "properties": {}
145
+ },
146
+ "ScaleItem": {
147
+ "methods": {},
148
+ "properties": {}
149
+ },
150
+ "SectionHeaderItem": {
151
+ "methods": {},
152
+ "properties": {}
153
+ },
154
+ "TextItem": {
155
+ "methods": {},
156
+ "properties": {}
157
+ },
158
+ "TextValidation": {
159
+ "methods": {},
160
+ "properties": {}
161
+ },
162
+ "TextValidationBuilder": {
163
+ "methods": {},
164
+ "properties": {}
165
+ },
166
+ "TimeItem": {
167
+ "methods": {},
168
+ "properties": {}
169
+ },
170
+ "VideoItem": {
171
+ "methods": {},
172
+ "properties": {}
173
+ }
174
+ }
175
+ }
176
+ }
@@ -0,0 +1 @@
1
+ ["forms"]
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "Document Service",
3
+ "link": "https://developers.google.com/apps-script/reference/document",
4
+ "classes": {
5
+ "DocumentApp": {
6
+ "methods": {
7
+ "create": {
8
+ "link": "https://developers.google.com/apps-script/reference/document/document-app#create(String)",
9
+ "returns": {
10
+ "type": "Document",
11
+ "link": "https://developers.google.com/apps-script/reference/document/document"
12
+ }
13
+ },
14
+ "properties": {
15
+ "Attribute": {
16
+ "type": "Attribute",
17
+ "link": "https://developers.google.com/apps-script/reference/document/attribute"
18
+ }
19
+ }
20
+ },
21
+ "Bookmark": {
22
+ "methods": {
23
+ "getId": {
24
+ "link": "https://developers.google.com/apps-script/reference/document/bookmark#getId()",
25
+ "returns": {
26
+ "type": "String",
27
+ "link": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }