@google/clasp 2.5.0 → 3.0.0-alpha1
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 +251 -196
- package/build/src/auth/auth.js +176 -0
- package/build/src/auth/auth_code_flow.js +36 -0
- package/build/src/auth/credential_store.js +1 -0
- package/build/src/auth/file_credential_store.js +82 -0
- package/build/src/auth/localhost_auth_code_flow.js +62 -0
- package/build/src/auth/serverless_auth_code_flow.js +32 -0
- package/build/src/commands/clone-script.js +71 -0
- package/build/src/commands/create-deployment.js +33 -0
- package/build/src/commands/create-script.js +75 -0
- package/build/src/commands/create-version.js +31 -0
- package/build/src/commands/delete-deployment.js +71 -0
- package/build/src/commands/disable-api.js +19 -0
- package/build/src/commands/enable-api.js +31 -0
- package/build/src/commands/list-apis.js +23 -0
- package/build/src/commands/list-deployments.js +30 -0
- package/build/src/commands/list-scripts.js +28 -0
- package/build/src/commands/list-versions.js +29 -0
- package/build/src/commands/login.js +53 -54
- package/build/src/commands/logout.js +15 -15
- package/build/src/commands/open-apis.js +11 -0
- package/build/src/commands/open-container.js +15 -0
- package/build/src/commands/open-credentials.js +11 -0
- package/build/src/commands/open-logs.js +11 -0
- package/build/src/commands/open-script.js +18 -0
- package/build/src/commands/open-webapp.js +56 -0
- package/build/src/commands/program.js +108 -0
- package/build/src/commands/pull.js +19 -18
- package/build/src/commands/push.js +64 -74
- package/build/src/commands/run-function.js +61 -0
- package/build/src/commands/setup-logs.js +12 -0
- package/build/src/commands/show-authorized-user.js +18 -0
- package/build/src/commands/show-file-status.js +34 -0
- package/build/src/commands/tail-logs.js +92 -0
- package/build/src/commands/utils.js +82 -0
- package/build/src/constants.js +0 -3
- package/build/src/{apis.js → core/apis.js} +90 -92
- package/build/src/core/clasp.js +171 -0
- package/build/src/core/files.js +359 -0
- package/build/src/core/functions.js +51 -0
- package/build/src/core/logs.js +41 -0
- package/build/src/core/manifest.js +1 -0
- package/build/src/core/project.js +313 -0
- package/build/src/core/services.js +179 -0
- package/build/src/core/utils.js +121 -0
- package/build/src/index.js +16 -355
- package/build/src/intl.js +34 -0
- package/docs/README.md +0 -5
- package/docs/config-files.md +0 -1
- package/docs/run.md +2 -2
- package/package.json +86 -51
- package/CHANGELOG.md +0 -79
- package/build/src/apis.d.ts +0 -34
- package/build/src/apis.js.map +0 -1
- package/build/src/apiutils.d.ts +0 -16
- package/build/src/apiutils.js +0 -81
- package/build/src/apiutils.js.map +0 -1
- package/build/src/auth.d.ts +0 -37
- package/build/src/auth.js +0 -310
- package/build/src/auth.js.map +0 -1
- package/build/src/clasp-error.d.ts +0 -3
- package/build/src/clasp-error.js +0 -10
- package/build/src/clasp-error.js.map +0 -1
- package/build/src/commands/apis.d.ts +0 -10
- package/build/src/commands/apis.js +0 -90
- package/build/src/commands/apis.js.map +0 -1
- package/build/src/commands/clone.d.ts +0 -13
- package/build/src/commands/clone.js +0 -60
- package/build/src/commands/clone.js.map +0 -1
- package/build/src/commands/create.d.ts +0 -16
- package/build/src/commands/create.js +0 -81
- package/build/src/commands/create.js.map +0 -1
- package/build/src/commands/default.d.ts +0 -8
- package/build/src/commands/default.js +0 -10
- package/build/src/commands/default.js.map +0 -1
- package/build/src/commands/deploy.d.ts +0 -13
- package/build/src/commands/deploy.js +0 -51
- package/build/src/commands/deploy.js.map +0 -1
- package/build/src/commands/deployments.d.ts +0 -5
- package/build/src/commands/deployments.js +0 -29
- package/build/src/commands/deployments.js.map +0 -1
- package/build/src/commands/list.d.ts +0 -9
- package/build/src/commands/list.js +0 -34
- package/build/src/commands/list.js.map +0 -1
- package/build/src/commands/login.d.ts +0 -15
- package/build/src/commands/login.js.map +0 -1
- package/build/src/commands/logout.d.ts +0 -5
- package/build/src/commands/logout.js.map +0 -1
- package/build/src/commands/logs.d.ts +0 -17
- package/build/src/commands/logs.js +0 -181
- package/build/src/commands/logs.js.map +0 -1
- package/build/src/commands/open.d.ts +0 -15
- package/build/src/commands/open.js +0 -89
- package/build/src/commands/open.js.map +0 -1
- package/build/src/commands/pull.d.ts +0 -10
- package/build/src/commands/pull.js.map +0 -1
- package/build/src/commands/push.d.ts +0 -11
- package/build/src/commands/push.js.map +0 -1
- package/build/src/commands/run.d.ts +0 -14
- package/build/src/commands/run.js +0 -130
- package/build/src/commands/run.js.map +0 -1
- package/build/src/commands/setting.d.ts +0 -8
- package/build/src/commands/setting.js +0 -53
- package/build/src/commands/setting.js.map +0 -1
- package/build/src/commands/status.d.ts +0 -9
- package/build/src/commands/status.js +0 -25
- package/build/src/commands/status.js.map +0 -1
- package/build/src/commands/undeploy.d.ts +0 -9
- package/build/src/commands/undeploy.js +0 -55
- package/build/src/commands/undeploy.js.map +0 -1
- package/build/src/commands/version.d.ts +0 -5
- package/build/src/commands/version.js +0 -22
- package/build/src/commands/version.js.map +0 -1
- package/build/src/commands/versions.d.ts +0 -5
- package/build/src/commands/versions.js +0 -41
- package/build/src/commands/versions.js.map +0 -1
- package/build/src/conf.d.ts +0 -40
- package/build/src/conf.js +0 -100
- package/build/src/conf.js.map +0 -1
- package/build/src/constants.d.ts +0 -6
- package/build/src/constants.js.map +0 -1
- package/build/src/dotfile.d.ts +0 -50
- package/build/src/dotfile.js +0 -71
- package/build/src/dotfile.js.map +0 -1
- package/build/src/files.d.ts +0 -70
- package/build/src/files.js +0 -364
- package/build/src/files.js.map +0 -1
- package/build/src/index.d.ts +0 -18
- package/build/src/index.js.map +0 -1
- package/build/src/inquirer.d.ts +0 -82
- package/build/src/inquirer.js +0 -111
- package/build/src/inquirer.js.map +0 -1
- package/build/src/manifest.d.ts +0 -123
- package/build/src/manifest.js +0 -142
- package/build/src/manifest.js.map +0 -1
- package/build/src/messages.d.ts +0 -110
- package/build/src/messages.js +0 -161
- package/build/src/messages.js.map +0 -1
- package/build/src/urls.d.ts +0 -21
- package/build/src/urls.js +0 -33
- package/build/src/urls.js.map +0 -1
- package/build/src/utils.d.ts +0 -102
- package/build/src/utils.js +0 -232
- package/build/src/utils.js.map +0 -1
- package/docs/develop.md +0 -94
- package/docs/esmodules.md +0 -81
- package/docs/running-locally.md +0 -31
- package/docs/settings.md +0 -56
- package/docs/typescript.md +0 -354
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import Debug from 'debug';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { google } from 'googleapis';
|
|
4
|
+
import { fetchWithPages } from './utils.js';
|
|
5
|
+
import { assertAuthenticated, assertScriptConfigured, handleApiError } from './utils.js';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
const debug = Debug('clasp:core');
|
|
8
|
+
export class Project {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
get scriptId() {
|
|
13
|
+
var _a;
|
|
14
|
+
return (_a = this.options.project) === null || _a === void 0 ? void 0 : _a.scriptId;
|
|
15
|
+
}
|
|
16
|
+
get projectId() {
|
|
17
|
+
var _a;
|
|
18
|
+
return (_a = this.options.project) === null || _a === void 0 ? void 0 : _a.projectId;
|
|
19
|
+
}
|
|
20
|
+
get parentId() {
|
|
21
|
+
var _a;
|
|
22
|
+
return (_a = this.options.project) === null || _a === void 0 ? void 0 : _a.parentId;
|
|
23
|
+
}
|
|
24
|
+
// TODO - Do we need the assertion or can just use accessor?
|
|
25
|
+
getProjectId() {
|
|
26
|
+
assertScriptConfigured(this.options);
|
|
27
|
+
return this.options.project.projectId;
|
|
28
|
+
}
|
|
29
|
+
async createScript(name, parentId) {
|
|
30
|
+
var _a;
|
|
31
|
+
debug('Creating script %s', name);
|
|
32
|
+
assertAuthenticated(this.options);
|
|
33
|
+
if ((_a = this.options.project) === null || _a === void 0 ? void 0 : _a.scriptId) {
|
|
34
|
+
debug('Warning: Creating script while id already exists');
|
|
35
|
+
}
|
|
36
|
+
const credentials = this.options.credentials;
|
|
37
|
+
const script = google.script({ version: 'v1', auth: credentials });
|
|
38
|
+
try {
|
|
39
|
+
const requestOptions = {
|
|
40
|
+
requestBody: {
|
|
41
|
+
parentId,
|
|
42
|
+
title: name,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
debug('Creating project with request %O', requestOptions);
|
|
46
|
+
const res = await script.projects.create(requestOptions);
|
|
47
|
+
if (!res.data.scriptId) {
|
|
48
|
+
throw new Error('Unexpected error, script ID missing from response.');
|
|
49
|
+
}
|
|
50
|
+
debug('Created script %s', res.data.scriptId);
|
|
51
|
+
const scriptId = res.data.scriptId;
|
|
52
|
+
this.options.project = { scriptId, parentId };
|
|
53
|
+
return scriptId;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
handleApiError(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async createWithContainer(name, mimeType) {
|
|
60
|
+
var _a;
|
|
61
|
+
debug('Creating container bound script %s (%s)', name, mimeType);
|
|
62
|
+
assertAuthenticated(this.options);
|
|
63
|
+
if ((_a = this.options.project) === null || _a === void 0 ? void 0 : _a.scriptId) {
|
|
64
|
+
debug('Warning: Creating script while id already exists');
|
|
65
|
+
}
|
|
66
|
+
let parentId;
|
|
67
|
+
const credentials = this.options.credentials;
|
|
68
|
+
const drive = google.drive({ version: 'v3', auth: credentials });
|
|
69
|
+
try {
|
|
70
|
+
const requestOptions = {
|
|
71
|
+
requestBody: {
|
|
72
|
+
mimeType,
|
|
73
|
+
name,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
debug('Creating project with request %O', requestOptions);
|
|
77
|
+
const res = await drive.files.create(requestOptions);
|
|
78
|
+
parentId = res.data.id;
|
|
79
|
+
debug('Created container %s', parentId);
|
|
80
|
+
if (!parentId) {
|
|
81
|
+
throw new Error('Unexpected error, container ID missing from response.');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
handleApiError(error);
|
|
86
|
+
}
|
|
87
|
+
const scriptId = await this.createScript(name, parentId);
|
|
88
|
+
return {
|
|
89
|
+
parentId,
|
|
90
|
+
scriptId,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async listScripts() {
|
|
94
|
+
debug('Fetching scripts');
|
|
95
|
+
assertAuthenticated(this.options);
|
|
96
|
+
const credentials = this.options.credentials;
|
|
97
|
+
const drive = google.drive({ version: 'v3', auth: credentials });
|
|
98
|
+
try {
|
|
99
|
+
return fetchWithPages(async (pageSize, pageToken) => {
|
|
100
|
+
var _a, _b;
|
|
101
|
+
const requestOptions = {
|
|
102
|
+
pageSize,
|
|
103
|
+
pageToken,
|
|
104
|
+
fields: 'nextPageToken, files(id, name)',
|
|
105
|
+
q: 'mimeType="application/vnd.google-apps.script"',
|
|
106
|
+
};
|
|
107
|
+
debug('Fetching scripts from drive with request %O', requestOptions);
|
|
108
|
+
const res = await drive.files.list(requestOptions);
|
|
109
|
+
return {
|
|
110
|
+
results: ((_a = res.data.files) !== null && _a !== void 0 ? _a : []),
|
|
111
|
+
pageToken: (_b = res.data.nextPageToken) !== null && _b !== void 0 ? _b : undefined,
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
handleApiError(error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async version(description = '') {
|
|
120
|
+
var _a;
|
|
121
|
+
debug('Creating version: %s', description);
|
|
122
|
+
assertAuthenticated(this.options);
|
|
123
|
+
assertScriptConfigured(this.options);
|
|
124
|
+
const credentials = this.options.credentials;
|
|
125
|
+
const scriptId = this.options.project.scriptId;
|
|
126
|
+
const script = google.script({ version: 'v1', auth: credentials });
|
|
127
|
+
try {
|
|
128
|
+
const requestOptions = {
|
|
129
|
+
requestBody: {
|
|
130
|
+
description: description !== null && description !== void 0 ? description : '',
|
|
131
|
+
},
|
|
132
|
+
scriptId: scriptId,
|
|
133
|
+
};
|
|
134
|
+
debug('Creating version with request %O', requestOptions);
|
|
135
|
+
const res = await script.projects.versions.create(requestOptions);
|
|
136
|
+
const versionNumber = (_a = res.data.versionNumber) !== null && _a !== void 0 ? _a : 0;
|
|
137
|
+
debug('Created new version %d', versionNumber);
|
|
138
|
+
return versionNumber;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
handleApiError(error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async listVersions() {
|
|
145
|
+
debug('Fetching versions');
|
|
146
|
+
assertAuthenticated(this.options);
|
|
147
|
+
assertScriptConfigured(this.options);
|
|
148
|
+
const scriptId = this.options.project.scriptId;
|
|
149
|
+
const credentials = this.options.credentials;
|
|
150
|
+
const script = google.script({ version: 'v1', auth: credentials });
|
|
151
|
+
try {
|
|
152
|
+
return fetchWithPages(async (pageSize, pageToken) => {
|
|
153
|
+
var _a, _b;
|
|
154
|
+
const requestOptions = {
|
|
155
|
+
scriptId,
|
|
156
|
+
pageSize,
|
|
157
|
+
pageToken,
|
|
158
|
+
};
|
|
159
|
+
debug('Fetching versions with request %O', requestOptions);
|
|
160
|
+
const res = await script.projects.versions.list(requestOptions);
|
|
161
|
+
return {
|
|
162
|
+
results: (_a = res.data.versions) !== null && _a !== void 0 ? _a : [],
|
|
163
|
+
pageToken: (_b = res.data.nextPageToken) !== null && _b !== void 0 ? _b : undefined,
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
handleApiError(error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async listDeployments() {
|
|
172
|
+
debug('Listing deployments');
|
|
173
|
+
assertAuthenticated(this.options);
|
|
174
|
+
assertScriptConfigured(this.options);
|
|
175
|
+
const scriptId = this.options.project.scriptId;
|
|
176
|
+
const credentials = this.options.credentials;
|
|
177
|
+
const script = google.script({ version: 'v1', auth: credentials });
|
|
178
|
+
try {
|
|
179
|
+
return fetchWithPages(async (pageSize, pageToken) => {
|
|
180
|
+
var _a, _b;
|
|
181
|
+
const requestOptions = {
|
|
182
|
+
scriptId,
|
|
183
|
+
pageSize,
|
|
184
|
+
pageToken,
|
|
185
|
+
};
|
|
186
|
+
debug('Fetching deployments with request %O', requestOptions);
|
|
187
|
+
const res = await script.projects.deployments.list(requestOptions);
|
|
188
|
+
return {
|
|
189
|
+
results: (_a = res.data.deployments) !== null && _a !== void 0 ? _a : [],
|
|
190
|
+
pageToken: (_b = res.data.nextPageToken) !== null && _b !== void 0 ? _b : undefined,
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
handleApiError(error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async deploy(description = '', deploymentId, versionNumber) {
|
|
199
|
+
debug('Deploying project: %s (%s)', description, versionNumber !== null && versionNumber !== void 0 ? versionNumber : 'HEAD');
|
|
200
|
+
assertAuthenticated(this.options);
|
|
201
|
+
assertScriptConfigured(this.options);
|
|
202
|
+
if (versionNumber === undefined) {
|
|
203
|
+
versionNumber = await this.version(description);
|
|
204
|
+
}
|
|
205
|
+
const scriptId = this.options.project.scriptId;
|
|
206
|
+
const credentials = this.options.credentials;
|
|
207
|
+
const script = google.script({ version: 'v1', auth: credentials });
|
|
208
|
+
try {
|
|
209
|
+
let deployment;
|
|
210
|
+
if (!deploymentId) {
|
|
211
|
+
const requestOptions = {
|
|
212
|
+
scriptId: scriptId,
|
|
213
|
+
requestBody: {
|
|
214
|
+
description: description !== null && description !== void 0 ? description : '',
|
|
215
|
+
versionNumber: versionNumber,
|
|
216
|
+
manifestFileName: 'appsscript',
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
debug('Creating deployment with request %O', requestOptions);
|
|
220
|
+
const res = await script.projects.deployments.create(requestOptions);
|
|
221
|
+
deployment = res.data;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const requestOptions = {
|
|
225
|
+
scriptId: scriptId,
|
|
226
|
+
deploymentId: deploymentId,
|
|
227
|
+
requestBody: {
|
|
228
|
+
deploymentConfig: {
|
|
229
|
+
description: description !== null && description !== void 0 ? description : '',
|
|
230
|
+
versionNumber: versionNumber,
|
|
231
|
+
scriptId: scriptId,
|
|
232
|
+
manifestFileName: 'appsscript',
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
debug('Updating existing deployment with request %O', requestOptions);
|
|
237
|
+
const res = await script.projects.deployments.update(requestOptions);
|
|
238
|
+
deployment = res.data;
|
|
239
|
+
}
|
|
240
|
+
return deployment;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
handleApiError(error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async entryPoints(deploymentId) {
|
|
247
|
+
var _a, _b;
|
|
248
|
+
assertAuthenticated(this.options);
|
|
249
|
+
assertScriptConfigured(this.options);
|
|
250
|
+
const scriptId = this.options.project.scriptId;
|
|
251
|
+
const credentials = this.options.credentials;
|
|
252
|
+
const script = google.script({ version: 'v1', auth: credentials });
|
|
253
|
+
try {
|
|
254
|
+
const res = await script.projects.deployments.get({ scriptId, deploymentId });
|
|
255
|
+
const entryPoints = (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.entryPoints) !== null && _b !== void 0 ? _b : [];
|
|
256
|
+
return entryPoints;
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
handleApiError(error);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async undeploy(deploymentId) {
|
|
263
|
+
debug('Deleting deployment %s', deploymentId);
|
|
264
|
+
assertAuthenticated(this.options);
|
|
265
|
+
assertScriptConfigured(this.options);
|
|
266
|
+
const scriptId = this.options.project.scriptId;
|
|
267
|
+
const credentials = this.options.credentials;
|
|
268
|
+
const script = google.script({ version: 'v1', auth: credentials });
|
|
269
|
+
try {
|
|
270
|
+
const requestOptions = {
|
|
271
|
+
scriptId: scriptId,
|
|
272
|
+
deploymentId,
|
|
273
|
+
};
|
|
274
|
+
debug('Deleting deployment with request %O', requestOptions);
|
|
275
|
+
await script.projects.deployments.delete(requestOptions);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
handleApiError(error);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async updateSettings() {
|
|
282
|
+
debug('Updating settings');
|
|
283
|
+
assertScriptConfigured(this.options);
|
|
284
|
+
const srcDir = path.relative(this.options.files.projectRootDir, this.options.files.contentDir);
|
|
285
|
+
const settings = {
|
|
286
|
+
scriptId: this.options.project.scriptId,
|
|
287
|
+
rootDir: srcDir,
|
|
288
|
+
projectId: this.options.project.projectId,
|
|
289
|
+
fileExtension: this.options.files.fileExtension,
|
|
290
|
+
filePushOrder: [],
|
|
291
|
+
};
|
|
292
|
+
await fs.writeFile(this.options.configFilePath, JSON.stringify(settings, null, 2));
|
|
293
|
+
}
|
|
294
|
+
async setProjectId(projectId) {
|
|
295
|
+
debug('Setting project ID %s in file %s', projectId, this.options.configFilePath);
|
|
296
|
+
assertScriptConfigured(this.options);
|
|
297
|
+
this.options.project.projectId = projectId;
|
|
298
|
+
this.updateSettings();
|
|
299
|
+
}
|
|
300
|
+
exists() {
|
|
301
|
+
var _a;
|
|
302
|
+
return ((_a = this.options.project) === null || _a === void 0 ? void 0 : _a.scriptId) !== undefined;
|
|
303
|
+
}
|
|
304
|
+
async readManifest() {
|
|
305
|
+
debug('Reading manifest');
|
|
306
|
+
assertScriptConfigured(this.options);
|
|
307
|
+
const manifestPath = path.join(this.options.files.contentDir, 'appsscript.json');
|
|
308
|
+
debug('Manifest path is %s', manifestPath);
|
|
309
|
+
const content = await fs.readFile(manifestPath);
|
|
310
|
+
const manifest = JSON.parse(content.toString());
|
|
311
|
+
return manifest;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import Debug from 'debug';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import { google } from 'googleapis';
|
|
5
|
+
import { PUBLIC_ADVANCED_SERVICES } from './apis.js';
|
|
6
|
+
import { assertGcpProjectConfigured, handleApiError } from './utils.js';
|
|
7
|
+
import { fetchWithPages } from './utils.js';
|
|
8
|
+
const debug = Debug('clasp:core');
|
|
9
|
+
export class Services {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.options = config;
|
|
12
|
+
}
|
|
13
|
+
async getEnabledServices() {
|
|
14
|
+
debug('Fetching enabled services');
|
|
15
|
+
assertGcpProjectConfigured(this.options);
|
|
16
|
+
const projectId = this.options.project.projectId;
|
|
17
|
+
const serviceUsage = google.serviceusage({ version: 'v1', auth: this.options.credentials });
|
|
18
|
+
try {
|
|
19
|
+
const serviceList = await fetchWithPages(async (pageSize, pageToken) => {
|
|
20
|
+
var _a, _b;
|
|
21
|
+
const requestOptions = {
|
|
22
|
+
parent: `projects/${projectId}`,
|
|
23
|
+
filter: 'state:ENABLED',
|
|
24
|
+
pageSize,
|
|
25
|
+
pageToken,
|
|
26
|
+
};
|
|
27
|
+
debug('Fetching available APIs with request %O', requestOptions);
|
|
28
|
+
const res = await serviceUsage.services.list(requestOptions);
|
|
29
|
+
return {
|
|
30
|
+
results: (_a = res.data.services) !== null && _a !== void 0 ? _a : [],
|
|
31
|
+
pageToken: (_b = res.data.nextPageToken) !== null && _b !== void 0 ? _b : undefined,
|
|
32
|
+
};
|
|
33
|
+
}, {
|
|
34
|
+
pageSize: 200,
|
|
35
|
+
maxResults: 10000,
|
|
36
|
+
});
|
|
37
|
+
// Filter out the disabled ones. Print the enabled ones.
|
|
38
|
+
const truncateName = (name) => {
|
|
39
|
+
const i = name.indexOf('.');
|
|
40
|
+
if (i !== -1) {
|
|
41
|
+
return name.slice(0, i);
|
|
42
|
+
}
|
|
43
|
+
return name;
|
|
44
|
+
};
|
|
45
|
+
const allowedIds = PUBLIC_ADVANCED_SERVICES.map(service => service.serviceId);
|
|
46
|
+
return serviceList.results
|
|
47
|
+
.map(service => {
|
|
48
|
+
var _a, _b, _c, _d, _e, _f;
|
|
49
|
+
return ({
|
|
50
|
+
id: (_a = service.name) !== null && _a !== void 0 ? _a : '',
|
|
51
|
+
name: truncateName((_c = (_b = service.config) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : 'Unknown name'),
|
|
52
|
+
description: (_f = (_e = (_d = service.config) === null || _d === void 0 ? void 0 : _d.documentation) === null || _e === void 0 ? void 0 : _e.summary) !== null && _f !== void 0 ? _f : '',
|
|
53
|
+
});
|
|
54
|
+
})
|
|
55
|
+
.filter(service => {
|
|
56
|
+
return allowedIds.indexOf(service.name) !== -1;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
handleApiError(error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async getAvailableServices() {
|
|
64
|
+
var _a;
|
|
65
|
+
debug('Fetching available services');
|
|
66
|
+
const discovery = google.discovery({ version: 'v1' });
|
|
67
|
+
try {
|
|
68
|
+
const { data } = await discovery.apis.list({
|
|
69
|
+
preferred: true,
|
|
70
|
+
});
|
|
71
|
+
const allowedIds = PUBLIC_ADVANCED_SERVICES.map(service => service.serviceId);
|
|
72
|
+
const allServices = (_a = data.items) !== null && _a !== void 0 ? _a : [];
|
|
73
|
+
const isValidService = (s) => {
|
|
74
|
+
return (s.id !== undefined && s.name !== undefined && allowedIds.indexOf(s.name) !== -1 && s.description !== undefined);
|
|
75
|
+
};
|
|
76
|
+
const services = allServices.filter(isValidService).sort((a, b) => a.id.localeCompare(b.id));
|
|
77
|
+
debug('Available services: %O', services);
|
|
78
|
+
return services;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
handleApiError(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async enableService(serviceName) {
|
|
85
|
+
var _a;
|
|
86
|
+
debug('Enabling service %s', serviceName);
|
|
87
|
+
assertGcpProjectConfigured(this.options);
|
|
88
|
+
const projectId = this.options.project.projectId;
|
|
89
|
+
const contentDir = this.options.files.contentDir;
|
|
90
|
+
if (!serviceName) {
|
|
91
|
+
throw new Error('No service name provided.');
|
|
92
|
+
}
|
|
93
|
+
const manifestPath = path.join(contentDir, 'appsscript.json');
|
|
94
|
+
const manifestExists = await hasReadWriteAccess(manifestPath);
|
|
95
|
+
if (!manifestExists) {
|
|
96
|
+
debug('Manifest file at %s does not exist', manifestPath);
|
|
97
|
+
throw new Error('Manifest file does not exist.');
|
|
98
|
+
}
|
|
99
|
+
const advancedService = PUBLIC_ADVANCED_SERVICES.find(service => service.serviceId === serviceName);
|
|
100
|
+
if (!advancedService) {
|
|
101
|
+
throw new Error('Service is not a valid advanced service.');
|
|
102
|
+
}
|
|
103
|
+
// Do not update manifest if not valid advanced service
|
|
104
|
+
debug('Service is an advanced service, updating manifest');
|
|
105
|
+
const content = await fs.readFile(manifestPath);
|
|
106
|
+
const manifest = JSON.parse(content.toString());
|
|
107
|
+
if ((_a = manifest.dependencies) === null || _a === void 0 ? void 0 : _a.enabledAdvancedServices) {
|
|
108
|
+
if (manifest.dependencies.enabledAdvancedServices.findIndex(s => s.userSymbol === advancedService.userSymbol) === -1) {
|
|
109
|
+
manifest.dependencies.enabledAdvancedServices.push(advancedService);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (manifest.dependencies) {
|
|
113
|
+
manifest.dependencies.enabledAdvancedServices = [advancedService];
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
manifest.dependencies = { enabledAdvancedServices: [advancedService] };
|
|
117
|
+
}
|
|
118
|
+
debug('Updating manifest at %s with %j', manifestPath, manifest);
|
|
119
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
120
|
+
debug('Enabling GCP service %s.googleapis.com', serviceName);
|
|
121
|
+
const serviceUsage = google.serviceusage({ version: 'v1', auth: this.options.credentials });
|
|
122
|
+
const resourceName = `projects/${projectId}/services/${serviceName}.googleapis.com`;
|
|
123
|
+
try {
|
|
124
|
+
await serviceUsage.services.enable({ name: resourceName });
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
handleApiError(error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async disableService(serviceName) {
|
|
131
|
+
var _a;
|
|
132
|
+
debug('Disabling service %s', serviceName);
|
|
133
|
+
assertGcpProjectConfigured(this.options);
|
|
134
|
+
const projectId = this.options.project.projectId;
|
|
135
|
+
const contentDir = this.options.files.contentDir;
|
|
136
|
+
if (!serviceName) {
|
|
137
|
+
throw new Error('No service name provided.');
|
|
138
|
+
}
|
|
139
|
+
const manifestPath = path.join(contentDir, 'appsscript.json');
|
|
140
|
+
const manifestExists = await hasReadWriteAccess(manifestPath);
|
|
141
|
+
if (!manifestExists) {
|
|
142
|
+
debug('Manifest file at %s does not exist', manifestPath);
|
|
143
|
+
throw new Error('Manifest file does not exist.');
|
|
144
|
+
}
|
|
145
|
+
const advancedService = PUBLIC_ADVANCED_SERVICES.find(service => service.serviceId === serviceName);
|
|
146
|
+
if (!advancedService) {
|
|
147
|
+
throw new Error('Service is not a valid advanced service.');
|
|
148
|
+
}
|
|
149
|
+
// Do not update manifest if not valid advanced service
|
|
150
|
+
debug('Service is an advanced service, updating manifest');
|
|
151
|
+
const content = await fs.readFile(manifestPath);
|
|
152
|
+
const manifest = JSON.parse(content.toString());
|
|
153
|
+
if (!((_a = manifest.dependencies) === null || _a === void 0 ? void 0 : _a.enabledAdvancedServices)) {
|
|
154
|
+
debug('Service enabled in manifest, skipping manifest update');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
manifest.dependencies.enabledAdvancedServices = manifest.dependencies.enabledAdvancedServices.filter(service => service.serviceId !== serviceName);
|
|
158
|
+
debug('Updating manifest at %s with %j', manifestPath, manifest);
|
|
159
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
160
|
+
debug('Disabling GCP service %s.googleapis.com', serviceName);
|
|
161
|
+
const serviceUsage = google.serviceusage({ version: 'v1', auth: this.options.credentials });
|
|
162
|
+
const resourceName = `projects/${projectId}/services/${serviceName}.googleapis.com`;
|
|
163
|
+
try {
|
|
164
|
+
await serviceUsage.services.disable({ name: resourceName });
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
handleApiError(error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function hasReadWriteAccess(path) {
|
|
172
|
+
try {
|
|
173
|
+
await fs.access(path, fs.constants.W_OK | fs.constants.R_OK);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import Debug from 'debug';
|
|
2
|
+
import { GaxiosError } from 'googleapis-common';
|
|
3
|
+
const debug = Debug('clasp:core');
|
|
4
|
+
export function assertAuthenticated(options) {
|
|
5
|
+
if (!options.credentials) {
|
|
6
|
+
debug('Credentials not set in options: %O', options);
|
|
7
|
+
throw new Error('No credentials found.', {
|
|
8
|
+
cause: {
|
|
9
|
+
code: 'NO_CREDENTIALS',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function assertScriptConfigured(options) {
|
|
15
|
+
var _a;
|
|
16
|
+
if (!((_a = options.project) === null || _a === void 0 ? void 0 : _a.scriptId) ||
|
|
17
|
+
!options.files.projectRootDir ||
|
|
18
|
+
!options.configFilePath ||
|
|
19
|
+
!options.files.contentDir) {
|
|
20
|
+
debug('Script configuration not found in options: %O', options);
|
|
21
|
+
throw new Error('Project settings not found.', {
|
|
22
|
+
cause: {
|
|
23
|
+
code: 'MISSING_SCRIPT_CONFIGURATION',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function assertGcpProjectConfigured(options) {
|
|
29
|
+
var _a;
|
|
30
|
+
assertScriptConfigured(options);
|
|
31
|
+
if (!((_a = options.project) === null || _a === void 0 ? void 0 : _a.projectId)) {
|
|
32
|
+
debug('Script configuration not found in options: %O', options);
|
|
33
|
+
throw new Error('Project ID not found.', {
|
|
34
|
+
cause: {
|
|
35
|
+
code: 'MISSING_PROJECT_ID',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function pageOptionsWithDefaults(options) {
|
|
41
|
+
return {
|
|
42
|
+
pageSize: 100,
|
|
43
|
+
maxPages: 10,
|
|
44
|
+
maxResults: Number.MAX_SAFE_INTEGER,
|
|
45
|
+
...(options !== null && options !== void 0 ? options : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export async function fetchWithPages(fn, options) {
|
|
49
|
+
const { pageSize, maxPages, maxResults } = pageOptionsWithDefaults(options);
|
|
50
|
+
let pageToken = undefined;
|
|
51
|
+
let pageCount = 0;
|
|
52
|
+
const results = [];
|
|
53
|
+
do {
|
|
54
|
+
debug('Fetching page %d', pageCount + 1);
|
|
55
|
+
const page = await fn(pageSize, pageToken);
|
|
56
|
+
if (page.results) {
|
|
57
|
+
results.push(...page.results);
|
|
58
|
+
}
|
|
59
|
+
++pageCount;
|
|
60
|
+
pageToken = page.pageToken;
|
|
61
|
+
} while (pageToken && pageCount < maxPages && results.length < maxResults);
|
|
62
|
+
if (pageToken) {
|
|
63
|
+
debug('Returning partial results, page limit exceeded');
|
|
64
|
+
}
|
|
65
|
+
if (results.length > maxResults) {
|
|
66
|
+
debug('Trimming results to %d', maxResults);
|
|
67
|
+
return {
|
|
68
|
+
results: results.slice(0, maxResults),
|
|
69
|
+
partialResults: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
results,
|
|
74
|
+
partialResults: pageToken !== undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function isDetailedError(error) {
|
|
78
|
+
if (!error) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const detailedError = error;
|
|
82
|
+
if (detailedError.errors === undefined) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (detailedError.errors.length === 0) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
const ERROR_CODES = {
|
|
91
|
+
400: 'INVALID_ARGUMENT',
|
|
92
|
+
401: 'NOT_AUTHENTICATED',
|
|
93
|
+
403: 'NOT_AUTHORIZED',
|
|
94
|
+
404: 'NOT_FOUND',
|
|
95
|
+
};
|
|
96
|
+
export function handleApiError(error) {
|
|
97
|
+
var _a;
|
|
98
|
+
debug('Handling API error: %O', error);
|
|
99
|
+
if (!(error instanceof GaxiosError)) {
|
|
100
|
+
throw new Error('Unexpected error', {
|
|
101
|
+
cause: {
|
|
102
|
+
code: 'UNEPECTED_ERROR',
|
|
103
|
+
message: String(error),
|
|
104
|
+
error: error,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const status = error.status;
|
|
109
|
+
let message = error.message;
|
|
110
|
+
if (isDetailedError(error)) {
|
|
111
|
+
message = error.errors[0].message;
|
|
112
|
+
}
|
|
113
|
+
const code = (_a = ERROR_CODES[status !== null && status !== void 0 ? status : 0]) !== null && _a !== void 0 ? _a : 'UNEXPECTED_API_ERROR';
|
|
114
|
+
throw new Error(message, {
|
|
115
|
+
cause: {
|
|
116
|
+
code: code,
|
|
117
|
+
message: message,
|
|
118
|
+
error: error,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|