@google/clasp 3.0.6-alpha → 3.1.1
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 +35 -2
- package/build/src/auth/auth.js +54 -10
- package/build/src/auth/auth_code_flow.js +51 -0
- package/build/src/auth/credential_store.js +13 -0
- package/build/src/auth/file_credential_store.js +62 -7
- package/build/src/auth/localhost_auth_code_flow.js +47 -5
- package/build/src/auth/serverless_auth_code_flow.js +39 -2
- package/build/src/commands/clone-script.js +37 -5
- package/build/src/commands/create-deployment.js +31 -6
- package/build/src/commands/create-script.js +65 -24
- package/build/src/commands/create-version.js +21 -1
- package/build/src/commands/delete-deployment.js +36 -5
- package/build/src/commands/delete-script.js +41 -0
- package/build/src/commands/disable-api.js +20 -1
- package/build/src/commands/enable-api.js +20 -1
- package/build/src/commands/list-apis.js +24 -1
- package/build/src/commands/list-deployments.js +35 -5
- package/build/src/commands/list-scripts.js +26 -2
- package/build/src/commands/list-versions.js +35 -7
- package/build/src/commands/login.js +36 -10
- package/build/src/commands/logout.js +23 -1
- package/build/src/commands/open-apis.js +20 -1
- package/build/src/commands/open-container.js +20 -1
- package/build/src/commands/open-credentials.js +20 -1
- package/build/src/commands/open-logs.js +20 -1
- package/build/src/commands/open-script.js +20 -1
- package/build/src/commands/open-webapp.js +20 -1
- package/build/src/commands/program.js +48 -7
- package/build/src/commands/pull.js +54 -13
- package/build/src/commands/push.js +49 -9
- package/build/src/commands/run-function.js +56 -13
- package/build/src/commands/setup-logs.js +20 -1
- package/build/src/commands/show-authorized-user.js +29 -2
- package/build/src/commands/show-file-status.js +17 -2
- package/build/src/commands/start-mcp.js +17 -1
- package/build/src/commands/tail-logs.js +20 -5
- package/build/src/commands/update-deployment.js +32 -6
- package/build/src/commands/utils.js +68 -0
- package/build/src/constants.js +15 -0
- package/build/src/core/apis.js +13 -3
- package/build/src/core/clasp.js +71 -12
- package/build/src/core/files.js +135 -32
- package/build/src/core/functions.js +36 -0
- package/build/src/core/logs.js +29 -0
- package/build/src/core/manifest.js +13 -0
- package/build/src/core/project.js +154 -7
- package/build/src/core/services.js +105 -16
- package/build/src/core/utils.js +57 -1
- package/build/src/experiments.js +23 -0
- package/build/src/index.js +2 -0
- package/build/src/intl.js +28 -0
- package/build/src/mcp/server.js +82 -6
- package/docs/run.md +10 -4
- package/package.json +3 -3
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
// Copyright 2025 Google LLC
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
// This file defines the `Project` class, which is responsible for managing
|
|
15
|
+
// Google Apps Script project metadata, lifecycle operations (creation, versions,
|
|
16
|
+
// deployments), and local project configuration settings.
|
|
1
17
|
import Debug from 'debug';
|
|
2
18
|
import fs from 'fs/promises';
|
|
3
19
|
import { google } from 'googleapis';
|
|
@@ -5,6 +21,12 @@ import { fetchWithPages } from './utils.js';
|
|
|
5
21
|
import { assertAuthenticated, assertScriptConfigured, handleApiError } from './utils.js';
|
|
6
22
|
import path from 'path';
|
|
7
23
|
const debug = Debug('clasp:core');
|
|
24
|
+
/**
|
|
25
|
+
* Manages Google Apps Script project settings and interactions with the
|
|
26
|
+
* Apps Script API for operations like creating projects, versions,
|
|
27
|
+
* and deployments. It also handles reading and writing the local
|
|
28
|
+
* `.clasp.json` configuration file and the `appsscript.json` manifest.
|
|
29
|
+
*/
|
|
8
30
|
export class Project {
|
|
9
31
|
constructor(options) {
|
|
10
32
|
this.options = options;
|
|
@@ -22,10 +44,23 @@ export class Project {
|
|
|
22
44
|
return (_a = this.options.project) === null || _a === void 0 ? void 0 : _a.parentId;
|
|
23
45
|
}
|
|
24
46
|
// TODO - Do we need the assertion or can just use accessor?
|
|
47
|
+
/**
|
|
48
|
+
* Retrieves the Google Cloud Platform (GCP) project ID associated with the script.
|
|
49
|
+
* Asserts that the script is configured before returning the ID.
|
|
50
|
+
* @returns {string | undefined} The GCP project ID, or undefined if not set.
|
|
51
|
+
* @throws {Error} If the script is not configured.
|
|
52
|
+
*/
|
|
25
53
|
getProjectId() {
|
|
26
54
|
assertScriptConfigured(this.options);
|
|
27
55
|
return this.options.project.projectId;
|
|
28
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Creates a new standalone Apps Script project.
|
|
59
|
+
* @param {string} name - The title for the new script project.
|
|
60
|
+
* @param {string} [parentId] - Optional ID of a Google Drive folder to create the script in.
|
|
61
|
+
* @returns {Promise<string>} A promise that resolves to the script ID of the newly created project.
|
|
62
|
+
* @throws {Error} If there's an API error or authentication issues.
|
|
63
|
+
*/
|
|
29
64
|
async createScript(name, parentId) {
|
|
30
65
|
var _a;
|
|
31
66
|
debug('Creating script %s', name);
|
|
@@ -56,6 +91,40 @@ export class Project {
|
|
|
56
91
|
handleApiError(error);
|
|
57
92
|
}
|
|
58
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Moves the specified Google Drive file to the trash.
|
|
96
|
+
* @returns {Promise<void>} A promise that resolves when the file is successfully trashed.
|
|
97
|
+
*/
|
|
98
|
+
async trashScript() {
|
|
99
|
+
var _a;
|
|
100
|
+
debug('Deleting script %s', (_a = this.options.project) === null || _a === void 0 ? void 0 : _a.scriptId);
|
|
101
|
+
assertAuthenticated(this.options);
|
|
102
|
+
assertScriptConfigured(this.options);
|
|
103
|
+
const fileId = this.options.project.scriptId;
|
|
104
|
+
const credentials = this.options.credentials;
|
|
105
|
+
const drive = google.drive({ version: 'v3', auth: credentials });
|
|
106
|
+
try {
|
|
107
|
+
const requestOptions = {
|
|
108
|
+
fileId,
|
|
109
|
+
requestBody: {
|
|
110
|
+
trashed: true,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
debug('Trashing script with request %O', requestOptions);
|
|
114
|
+
await drive.files.update(requestOptions);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
handleApiError(error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Creates a new Google Drive file (e.g., Sheet, Doc) and a bound Apps Script project for it.
|
|
122
|
+
* @param {string} name - The title for the new Drive file and script project.
|
|
123
|
+
* @param {string} mimeType - The MIME type of the Drive file to create (e.g., 'application/vnd.google-apps.spreadsheet').
|
|
124
|
+
* @returns {Promise<{scriptId: string; parentId: string}>} A promise that resolves to an object
|
|
125
|
+
* containing the script ID and the parent Drive file ID.
|
|
126
|
+
* @throws {Error} If there's an API error or authentication issues.
|
|
127
|
+
*/
|
|
59
128
|
async createWithContainer(name, mimeType) {
|
|
60
129
|
var _a;
|
|
61
130
|
debug('Creating container bound script %s (%s)', name, mimeType);
|
|
@@ -66,6 +135,7 @@ export class Project {
|
|
|
66
135
|
let parentId;
|
|
67
136
|
const credentials = this.options.credentials;
|
|
68
137
|
const drive = google.drive({ version: 'v3', auth: credentials });
|
|
138
|
+
// Create the container file (e.g., Google Sheet, Doc) using the Drive API.
|
|
69
139
|
try {
|
|
70
140
|
const requestOptions = {
|
|
71
141
|
requestBody: {
|
|
@@ -75,7 +145,7 @@ export class Project {
|
|
|
75
145
|
};
|
|
76
146
|
debug('Creating project with request %O', requestOptions);
|
|
77
147
|
const res = await drive.files.create(requestOptions);
|
|
78
|
-
parentId = res.data.id;
|
|
148
|
+
parentId = res.data.id; // Get the ID of the newly created container file.
|
|
79
149
|
debug('Created container %s', parentId);
|
|
80
150
|
if (!parentId) {
|
|
81
151
|
throw new Error('Unexpected error, container ID missing from response.');
|
|
@@ -84,12 +154,20 @@ export class Project {
|
|
|
84
154
|
catch (error) {
|
|
85
155
|
handleApiError(error);
|
|
86
156
|
}
|
|
157
|
+
// Once the container is created, create an Apps Script project bound to it.
|
|
87
158
|
const scriptId = await this.createScript(name, parentId);
|
|
88
159
|
return {
|
|
89
|
-
parentId,
|
|
160
|
+
parentId, // Return the ID of the container.
|
|
90
161
|
scriptId,
|
|
91
162
|
};
|
|
92
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Lists Apps Script projects accessible by the authenticated user from Google Drive.
|
|
166
|
+
* @returns {Promise<{results: Script[], partialResults: boolean} | undefined>}
|
|
167
|
+
* A promise that resolves to an object containing an array of script projects
|
|
168
|
+
* (with name and ID) and a flag indicating if results are partial, or undefined on error.
|
|
169
|
+
* @throws {Error} If there's an API error or authentication issues.
|
|
170
|
+
*/
|
|
93
171
|
async listScripts() {
|
|
94
172
|
debug('Fetching scripts');
|
|
95
173
|
assertAuthenticated(this.options);
|
|
@@ -116,6 +194,12 @@ export class Project {
|
|
|
116
194
|
handleApiError(error);
|
|
117
195
|
}
|
|
118
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Creates a new immutable version of the Apps Script project.
|
|
199
|
+
* @param {string} [description=''] - An optional description for the new version.
|
|
200
|
+
* @returns {Promise<number>} A promise that resolves to the newly created version number.
|
|
201
|
+
* @throws {Error} If there's an API error or authentication/configuration issues.
|
|
202
|
+
*/
|
|
119
203
|
async version(description = '') {
|
|
120
204
|
var _a;
|
|
121
205
|
debug('Creating version: %s', description);
|
|
@@ -141,6 +225,13 @@ export class Project {
|
|
|
141
225
|
handleApiError(error);
|
|
142
226
|
}
|
|
143
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Lists all immutable versions of the Apps Script project.
|
|
230
|
+
* @returns {Promise<{results: script_v1.Schema$Version[], partialResults: boolean} | undefined>}
|
|
231
|
+
* A promise that resolves to an object containing an array of version objects
|
|
232
|
+
* and a flag indicating if results are partial, or undefined on error.
|
|
233
|
+
* @throws {Error} If there's an API error or authentication/configuration issues.
|
|
234
|
+
*/
|
|
144
235
|
async listVersions() {
|
|
145
236
|
debug('Fetching versions');
|
|
146
237
|
assertAuthenticated(this.options);
|
|
@@ -168,6 +259,13 @@ export class Project {
|
|
|
168
259
|
handleApiError(error);
|
|
169
260
|
}
|
|
170
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Lists all deployments for the Apps Script project.
|
|
264
|
+
* @returns {Promise<{results: script_v1.Schema$Deployment[], partialResults: boolean} | undefined>}
|
|
265
|
+
* A promise that resolves to an object containing an array of deployment objects
|
|
266
|
+
* and a flag indicating if results are partial, or undefined on error.
|
|
267
|
+
* @throws {Error} If there's an API error or authentication/configuration issues.
|
|
268
|
+
*/
|
|
171
269
|
async listDeployments() {
|
|
172
270
|
debug('Listing deployments');
|
|
173
271
|
assertAuthenticated(this.options);
|
|
@@ -195,10 +293,21 @@ export class Project {
|
|
|
195
293
|
handleApiError(error);
|
|
196
294
|
}
|
|
197
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Creates a new deployment or updates an existing one for the Apps Script project.
|
|
298
|
+
* If `versionNumber` is not provided, a new script version is created with the given `description`.
|
|
299
|
+
* @param {string} [description=''] - Description for the new version (if created) or deployment.
|
|
300
|
+
* @param {string} [deploymentId] - Optional ID of an existing deployment to update. If not provided, a new deployment is created.
|
|
301
|
+
* @param {number} [versionNumber] - Optional specific script version number to deploy.
|
|
302
|
+
* @returns {Promise<script_v1.Schema$Deployment>} A promise that resolves to the deployment object.
|
|
303
|
+
* @throws {Error} If there's an API error or authentication/configuration issues.
|
|
304
|
+
*/
|
|
198
305
|
async deploy(description = '', deploymentId, versionNumber) {
|
|
199
306
|
debug('Deploying project: %s (%s)', description, versionNumber !== null && versionNumber !== void 0 ? versionNumber : 'HEAD');
|
|
200
307
|
assertAuthenticated(this.options);
|
|
201
308
|
assertScriptConfigured(this.options);
|
|
309
|
+
// If no specific versionNumber is provided for deployment,
|
|
310
|
+
// create a new version of the script with the given description.
|
|
202
311
|
if (versionNumber === undefined) {
|
|
203
312
|
versionNumber = await this.version(description);
|
|
204
313
|
}
|
|
@@ -207,9 +316,10 @@ export class Project {
|
|
|
207
316
|
const script = google.script({ version: 'v1', auth: credentials });
|
|
208
317
|
try {
|
|
209
318
|
let deployment;
|
|
319
|
+
// If no deploymentId is provided, create a new deployment.
|
|
210
320
|
if (!deploymentId) {
|
|
211
321
|
const requestOptions = {
|
|
212
|
-
scriptId: scriptId,
|
|
322
|
+
scriptId: scriptId, // The scriptId must be provided in the request body for create.
|
|
213
323
|
requestBody: {
|
|
214
324
|
description: description !== null && description !== void 0 ? description : '',
|
|
215
325
|
versionNumber: versionNumber,
|
|
@@ -221,14 +331,15 @@ export class Project {
|
|
|
221
331
|
deployment = res.data;
|
|
222
332
|
}
|
|
223
333
|
else {
|
|
334
|
+
// If a deploymentId is provided, update the existing deployment.
|
|
224
335
|
const requestOptions = {
|
|
225
|
-
scriptId: scriptId,
|
|
226
|
-
deploymentId: deploymentId,
|
|
336
|
+
scriptId: scriptId, // Path parameter for the scriptId.
|
|
337
|
+
deploymentId: deploymentId, // Path parameter for the deploymentId to update.
|
|
227
338
|
requestBody: {
|
|
228
339
|
deploymentConfig: {
|
|
229
340
|
description: description !== null && description !== void 0 ? description : '',
|
|
230
341
|
versionNumber: versionNumber,
|
|
231
|
-
scriptId: scriptId,
|
|
342
|
+
scriptId: scriptId, // The scriptId also needs to be in the deploymentConfig.
|
|
232
343
|
manifestFileName: 'appsscript',
|
|
233
344
|
},
|
|
234
345
|
},
|
|
@@ -237,12 +348,20 @@ export class Project {
|
|
|
237
348
|
const res = await script.projects.deployments.update(requestOptions);
|
|
238
349
|
deployment = res.data;
|
|
239
350
|
}
|
|
240
|
-
return deployment;
|
|
351
|
+
return deployment; // Return the created or updated deployment object.
|
|
241
352
|
}
|
|
242
353
|
catch (error) {
|
|
243
354
|
handleApiError(error);
|
|
244
355
|
}
|
|
245
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Retrieves the entry points for a specific deployment of the Apps Script project.
|
|
359
|
+
* Entry points define how the script can be executed (e.g., as a web app, API executable).
|
|
360
|
+
* @param {string} deploymentId - The ID of the deployment.
|
|
361
|
+
* @returns {Promise<script_v1.Schema$EntryPoint[] | undefined>} A promise that resolves to an array
|
|
362
|
+
* of entry point objects, or undefined if an error occurs.
|
|
363
|
+
* @throws {Error} If there's an API error or authentication/configuration issues.
|
|
364
|
+
*/
|
|
246
365
|
async entryPoints(deploymentId) {
|
|
247
366
|
var _a, _b;
|
|
248
367
|
assertAuthenticated(this.options);
|
|
@@ -259,6 +378,12 @@ export class Project {
|
|
|
259
378
|
handleApiError(error);
|
|
260
379
|
}
|
|
261
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Deletes a specific deployment of the Apps Script project.
|
|
383
|
+
* @param {string} deploymentId - The ID of the deployment to delete.
|
|
384
|
+
* @returns {Promise<void>} A promise that resolves when the deployment is deleted.
|
|
385
|
+
* @throws {Error} If there's an API error or authentication/configuration issues.
|
|
386
|
+
*/
|
|
262
387
|
async undeploy(deploymentId) {
|
|
263
388
|
debug('Deleting deployment %s', deploymentId);
|
|
264
389
|
assertAuthenticated(this.options);
|
|
@@ -278,6 +403,12 @@ export class Project {
|
|
|
278
403
|
handleApiError(error);
|
|
279
404
|
}
|
|
280
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Writes the current project settings (script ID, root directory, parent ID, project ID,
|
|
408
|
+
* file extensions, push order, skip subdirectories) to the `.clasp.json` file.
|
|
409
|
+
* @returns {Promise<void>} A promise that resolves when the settings are written.
|
|
410
|
+
* @throws {Error} If the script is not configured or there's a file system error.
|
|
411
|
+
*/
|
|
281
412
|
async updateSettings() {
|
|
282
413
|
debug('Updating settings');
|
|
283
414
|
assertScriptConfigured(this.options);
|
|
@@ -295,16 +426,32 @@ export class Project {
|
|
|
295
426
|
};
|
|
296
427
|
await fs.writeFile(this.options.configFilePath, JSON.stringify(settings, null, 2));
|
|
297
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Sets the Google Cloud Platform (GCP) project ID for the current Apps Script project
|
|
431
|
+
* and updates the `.clasp.json` file.
|
|
432
|
+
* @param {string | undefined} projectId - The GCP project ID to set.
|
|
433
|
+
* @returns {Promise<void>} A promise that resolves when the project ID is set and settings are updated.
|
|
434
|
+
* @throws {Error} If the script is not configured.
|
|
435
|
+
*/
|
|
298
436
|
async setProjectId(projectId) {
|
|
299
437
|
debug('Setting project ID %s in file %s', projectId, this.options.configFilePath);
|
|
300
438
|
assertScriptConfigured(this.options);
|
|
301
439
|
this.options.project.projectId = projectId;
|
|
302
440
|
this.updateSettings();
|
|
303
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* Checks if a script project is currently configured (i.e., if a script ID is set).
|
|
444
|
+
* @returns {boolean} True if a script ID is set, false otherwise.
|
|
445
|
+
*/
|
|
304
446
|
exists() {
|
|
305
447
|
var _a;
|
|
306
448
|
return ((_a = this.options.project) === null || _a === void 0 ? void 0 : _a.scriptId) !== undefined;
|
|
307
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Reads and parses the `appsscript.json` manifest file from the project's content directory.
|
|
452
|
+
* @returns {Promise<Manifest>} A promise that resolves to the parsed manifest object.
|
|
453
|
+
* @throws {Error} If the script is not configured or the manifest file cannot be read/parsed.
|
|
454
|
+
*/
|
|
308
455
|
async readManifest() {
|
|
309
456
|
debug('Reading manifest');
|
|
310
457
|
assertScriptConfigured(this.options);
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
// Copyright 2025 Google LLC
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
// This file defines the `Services` class, which handles the management of
|
|
15
|
+
// Google Cloud Platform (GCP) services and Advanced Google Services for an
|
|
16
|
+
// Apps Script project, including enabling, disabling, and listing services.
|
|
1
17
|
import path from 'path';
|
|
2
18
|
import Debug from 'debug';
|
|
3
19
|
import fs from 'fs/promises';
|
|
@@ -6,10 +22,23 @@ import { PUBLIC_ADVANCED_SERVICES } from './apis.js';
|
|
|
6
22
|
import { assertGcpProjectConfigured, handleApiError } from './utils.js';
|
|
7
23
|
import { fetchWithPages } from './utils.js';
|
|
8
24
|
const debug = Debug('clasp:core');
|
|
25
|
+
/**
|
|
26
|
+
* Manages the Google Cloud Platform (GCP) services and Advanced Google Services
|
|
27
|
+
* associated with an Apps Script project. This includes listing available and
|
|
28
|
+
* enabled services, as well as enabling or disabling services for the project.
|
|
29
|
+
*/
|
|
9
30
|
export class Services {
|
|
10
31
|
constructor(config) {
|
|
11
32
|
this.options = config;
|
|
12
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Retrieves a list of Google Cloud Platform (GCP) services that are currently
|
|
36
|
+
* enabled for the associated Apps Script project.
|
|
37
|
+
* @returns {Promise<Service[] | undefined>} A promise that resolves to an array of enabled
|
|
38
|
+
* services (with id, name, and description), or undefined if an error occurs.
|
|
39
|
+
* Filters for services that are also listed as public advanced services.
|
|
40
|
+
* @throws {Error} If the GCP project is not configured or authentication fails.
|
|
41
|
+
*/
|
|
13
42
|
async getEnabledServices() {
|
|
14
43
|
debug('Fetching enabled services');
|
|
15
44
|
assertGcpProjectConfigured(this.options);
|
|
@@ -35,24 +64,31 @@ export class Services {
|
|
|
35
64
|
maxResults: 10000,
|
|
36
65
|
});
|
|
37
66
|
// Filter out the disabled ones. Print the enabled ones.
|
|
67
|
+
// Filter out the disabled ones. Print the enabled ones.
|
|
38
68
|
const truncateName = (name) => {
|
|
69
|
+
// Service names from API might be like 'sheets.googleapis.com'.
|
|
70
|
+
// We only want the 'sheets' part for matching with PUBLIC_ADVANCED_SERVICES.
|
|
39
71
|
const i = name.indexOf('.');
|
|
40
72
|
if (i !== -1) {
|
|
41
73
|
return name.slice(0, i);
|
|
42
74
|
}
|
|
43
75
|
return name;
|
|
44
76
|
};
|
|
77
|
+
// Get a list of serviceIds from our known public advanced services.
|
|
45
78
|
const allowedIds = PUBLIC_ADVANCED_SERVICES.map(service => service.serviceId);
|
|
79
|
+
// Map the raw service list from API to our simplified `Service` type
|
|
80
|
+
// and filter them to include only those that are known public advanced services.
|
|
46
81
|
return serviceList.results
|
|
47
82
|
.map(service => {
|
|
48
83
|
var _a, _b, _c, _d, _e, _f;
|
|
49
84
|
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'),
|
|
85
|
+
id: (_a = service.name) !== null && _a !== void 0 ? _a : '', // Full name like 'sheets.googleapis.com'
|
|
86
|
+
name: truncateName((_c = (_b = service.config) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : 'Unknown name'), // Short name like 'sheets'
|
|
52
87
|
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
88
|
});
|
|
54
89
|
})
|
|
55
90
|
.filter(service => {
|
|
91
|
+
// Only include services that are in our `PUBLIC_ADVANCED_SERVICES` list.
|
|
56
92
|
return allowedIds.indexOf(service.name) !== -1;
|
|
57
93
|
});
|
|
58
94
|
}
|
|
@@ -60,19 +96,33 @@ export class Services {
|
|
|
60
96
|
handleApiError(error);
|
|
61
97
|
}
|
|
62
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Retrieves a list of all publicly available Google Advanced Services that can be
|
|
101
|
+
* enabled for an Apps Script project.
|
|
102
|
+
* @returns {Promise<Service[] | undefined>} A promise that resolves to an array of available
|
|
103
|
+
* services (with id, name, and description), or undefined if an error occurs.
|
|
104
|
+
* @throws {Error} If there's an API error.
|
|
105
|
+
*/
|
|
63
106
|
async getAvailableServices() {
|
|
64
107
|
var _a;
|
|
65
108
|
debug('Fetching available services');
|
|
66
109
|
const discovery = google.discovery({ version: 'v1' });
|
|
67
110
|
try {
|
|
111
|
+
// Fetch the list of all discoverable APIs. 'preferred: true' typically gets the recommended versions.
|
|
68
112
|
const { data } = await discovery.apis.list({
|
|
69
113
|
preferred: true,
|
|
70
114
|
});
|
|
115
|
+
// Get a list of serviceIds from our known public advanced services for filtering.
|
|
71
116
|
const allowedIds = PUBLIC_ADVANCED_SERVICES.map(service => service.serviceId);
|
|
72
117
|
const allServices = (_a = data.items) !== null && _a !== void 0 ? _a : [];
|
|
118
|
+
// Type guard to ensure the service item has the properties we expect and is a known advanced service.
|
|
73
119
|
const isValidService = (s) => {
|
|
74
|
-
return (s.id !== undefined &&
|
|
120
|
+
return (s.id !== undefined &&
|
|
121
|
+
s.name !== undefined &&
|
|
122
|
+
allowedIds.indexOf(s.name) !== -1 && // Check if the service's short name is in our list
|
|
123
|
+
s.description !== undefined);
|
|
75
124
|
};
|
|
125
|
+
// Filter the list of all discoverable APIs to only include valid, known advanced services.
|
|
76
126
|
const services = allServices.filter(isValidService).sort((a, b) => a.id.localeCompare(b.id));
|
|
77
127
|
debug('Available services: %O', services);
|
|
78
128
|
return services;
|
|
@@ -81,6 +131,17 @@ export class Services {
|
|
|
81
131
|
handleApiError(error);
|
|
82
132
|
}
|
|
83
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Enables a specified Google Advanced Service for the Apps Script project.
|
|
136
|
+
* This involves two steps:
|
|
137
|
+
* 1. Enabling the corresponding service in the Google Cloud Platform (GCP) project.
|
|
138
|
+
* 2. Updating the `appsscript.json` manifest file to include the service in its dependencies.
|
|
139
|
+
* @param {string} serviceName - The service ID (e.g., 'sheets', 'docs') of the service to enable.
|
|
140
|
+
* @returns {Promise<void>} A promise that resolves when the service is enabled.
|
|
141
|
+
* @throws {Error} If the service name is not provided, the manifest file doesn't exist,
|
|
142
|
+
* the service is not a valid advanced service, or if there's an API error or
|
|
143
|
+
* authentication/configuration issue.
|
|
144
|
+
*/
|
|
84
145
|
async enableService(serviceName) {
|
|
85
146
|
var _a;
|
|
86
147
|
debug('Enabling service %s', serviceName);
|
|
@@ -94,39 +155,59 @@ export class Services {
|
|
|
94
155
|
const manifestExists = await hasReadWriteAccess(manifestPath);
|
|
95
156
|
if (!manifestExists) {
|
|
96
157
|
debug('Manifest file at %s does not exist', manifestPath);
|
|
97
|
-
throw new Error('Manifest file does not exist.');
|
|
158
|
+
throw new Error('Manifest file does not exist.'); // Prerequisite: manifest must exist.
|
|
98
159
|
}
|
|
160
|
+
// Find the service details from our list of known public advanced services.
|
|
99
161
|
const advancedService = PUBLIC_ADVANCED_SERVICES.find(service => service.serviceId === serviceName);
|
|
100
162
|
if (!advancedService) {
|
|
101
|
-
throw new Error('Service is not a valid advanced service.');
|
|
163
|
+
throw new Error('Service is not a valid advanced service.'); // Ensure it's a known service.
|
|
102
164
|
}
|
|
103
|
-
//
|
|
165
|
+
// Update the manifest file to include the new service.
|
|
104
166
|
debug('Service is an advanced service, updating manifest');
|
|
105
167
|
const content = await fs.readFile(manifestPath);
|
|
106
168
|
const manifest = JSON.parse(content.toString());
|
|
169
|
+
// Ensure the dependencies structure exists and add the service if not already present.
|
|
107
170
|
if ((_a = manifest.dependencies) === null || _a === void 0 ? void 0 : _a.enabledAdvancedServices) {
|
|
171
|
+
// Check if the service (by its userSymbol) is already in the manifest.
|
|
108
172
|
if (manifest.dependencies.enabledAdvancedServices.findIndex(s => s.userSymbol === advancedService.userSymbol) === -1) {
|
|
109
173
|
manifest.dependencies.enabledAdvancedServices.push(advancedService);
|
|
110
174
|
}
|
|
111
175
|
}
|
|
112
176
|
else if (manifest.dependencies) {
|
|
177
|
+
// If 'dependencies' exists but 'enabledAdvancedServices' doesn't, create it.
|
|
113
178
|
manifest.dependencies.enabledAdvancedServices = [advancedService];
|
|
114
179
|
}
|
|
115
180
|
else {
|
|
181
|
+
// If 'dependencies' itself doesn't exist, create the full structure.
|
|
116
182
|
manifest.dependencies = { enabledAdvancedServices: [advancedService] };
|
|
117
183
|
}
|
|
118
184
|
debug('Updating manifest at %s with %j', manifestPath, manifest);
|
|
119
185
|
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
186
|
+
// Enable the corresponding service in the GCP project via the Service Usage API.
|
|
120
187
|
debug('Enabling GCP service %s.googleapis.com', serviceName);
|
|
121
188
|
const serviceUsage = google.serviceusage({ version: 'v1', auth: this.options.credentials });
|
|
122
|
-
const resourceName = `projects/${projectId}/services/${serviceName}.googleapis.com`;
|
|
189
|
+
const resourceName = `projects/${projectId}/services/${serviceName}.googleapis.com`; // Construct the service resource name.
|
|
123
190
|
try {
|
|
124
191
|
await serviceUsage.services.enable({ name: resourceName });
|
|
125
192
|
}
|
|
126
193
|
catch (error) {
|
|
194
|
+
// Note: If this GCP API call fails, the manifest will have been updated,
|
|
195
|
+
// but the service might not be enabled in GCP. This could lead to an inconsistent state.
|
|
196
|
+
// More robust error handling might involve reverting manifest changes or providing specific guidance.
|
|
127
197
|
handleApiError(error);
|
|
128
198
|
}
|
|
129
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Disables a specified Google Advanced Service for the Apps Script project.
|
|
202
|
+
* This involves two steps:
|
|
203
|
+
* 1. Disabling the corresponding service in the Google Cloud Platform (GCP) project.
|
|
204
|
+
* 2. Removing the service from the `appsscript.json` manifest file's dependencies.
|
|
205
|
+
* @param {string} serviceName - The service ID (e.g., 'sheets', 'docs') of the service to disable.
|
|
206
|
+
* @returns {Promise<void>} A promise that resolves when the service is disabled.
|
|
207
|
+
* @throws {Error} If the service name is not provided, the manifest file doesn't exist,
|
|
208
|
+
* the service is not a valid advanced service, or if there's an API error or
|
|
209
|
+
* authentication/configuration issue.
|
|
210
|
+
*/
|
|
130
211
|
async disableService(serviceName) {
|
|
131
212
|
var _a;
|
|
132
213
|
debug('Disabling service %s', serviceName);
|
|
@@ -140,30 +221,38 @@ export class Services {
|
|
|
140
221
|
const manifestExists = await hasReadWriteAccess(manifestPath);
|
|
141
222
|
if (!manifestExists) {
|
|
142
223
|
debug('Manifest file at %s does not exist', manifestPath);
|
|
143
|
-
throw new Error('Manifest file does not exist.');
|
|
224
|
+
throw new Error('Manifest file does not exist.'); // Prerequisite: manifest must exist.
|
|
144
225
|
}
|
|
226
|
+
// Find the service details from our list of known public advanced services.
|
|
145
227
|
const advancedService = PUBLIC_ADVANCED_SERVICES.find(service => service.serviceId === serviceName);
|
|
146
228
|
if (!advancedService) {
|
|
147
|
-
throw new Error('Service is not a valid advanced service.');
|
|
229
|
+
throw new Error('Service is not a valid advanced service.'); // Ensure it's a known service.
|
|
148
230
|
}
|
|
149
|
-
//
|
|
231
|
+
// Update the manifest file to remove the service.
|
|
150
232
|
debug('Service is an advanced service, updating manifest');
|
|
151
233
|
const content = await fs.readFile(manifestPath);
|
|
152
234
|
const manifest = JSON.parse(content.toString());
|
|
235
|
+
// If dependencies or enabledAdvancedServices array doesn't exist, or service not found, nothing to do for manifest.
|
|
153
236
|
if (!((_a = manifest.dependencies) === null || _a === void 0 ? void 0 : _a.enabledAdvancedServices)) {
|
|
154
|
-
debug('Service enabled in manifest, skipping manifest update');
|
|
155
|
-
|
|
237
|
+
debug('Service not listed as enabled in manifest, skipping manifest update for disabling.');
|
|
238
|
+
// Continue to attempt disabling at GCP level, as manifest might be out of sync.
|
|
156
239
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
240
|
+
else {
|
|
241
|
+
// Filter out the service to be disabled.
|
|
242
|
+
manifest.dependencies.enabledAdvancedServices = manifest.dependencies.enabledAdvancedServices.filter(service => service.serviceId !== serviceName);
|
|
243
|
+
debug('Updating manifest at %s with %j', manifestPath, manifest);
|
|
244
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
245
|
+
}
|
|
246
|
+
// Disable the corresponding service in the GCP project via the Service Usage API.
|
|
160
247
|
debug('Disabling GCP service %s.googleapis.com', serviceName);
|
|
161
248
|
const serviceUsage = google.serviceusage({ version: 'v1', auth: this.options.credentials });
|
|
162
|
-
const resourceName = `projects/${projectId}/services/${serviceName}.googleapis.com`;
|
|
249
|
+
const resourceName = `projects/${projectId}/services/${serviceName}.googleapis.com`; // Construct the service resource name.
|
|
163
250
|
try {
|
|
164
251
|
await serviceUsage.services.disable({ name: resourceName });
|
|
165
252
|
}
|
|
166
253
|
catch (error) {
|
|
254
|
+
// Note: Similar to enableService, if this GCP API call fails, the manifest might have been updated,
|
|
255
|
+
// potentially leading to an inconsistent state (service disabled in manifest but still active in GCP).
|
|
167
256
|
handleApiError(error);
|
|
168
257
|
}
|
|
169
258
|
}
|
package/build/src/core/utils.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
+
// Copyright 2025 Google LLC
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
// This file provides utility types, assertion functions, and helper functions
|
|
15
|
+
// (like for API pagination and error handling) used across the core modules
|
|
16
|
+
// of clasp.
|
|
1
17
|
import Debug from 'debug';
|
|
2
18
|
import { GaxiosError } from 'googleapis-common';
|
|
3
19
|
const debug = Debug('clasp:core');
|
|
20
|
+
/**
|
|
21
|
+
* Asserts that the provided ClaspOptions include credentials.
|
|
22
|
+
* Throws an error if credentials are not set. This also acts as a type guard.
|
|
23
|
+
* @param {ClaspOptions} options - The Clasp options to check.
|
|
24
|
+
* @throws {Error} If `options.credentials` is not set.
|
|
25
|
+
*/
|
|
4
26
|
export function assertAuthenticated(options) {
|
|
5
27
|
if (!options.credentials) {
|
|
6
28
|
debug('Credentials not set in options: %O', options);
|
|
@@ -11,6 +33,13 @@ export function assertAuthenticated(options) {
|
|
|
11
33
|
});
|
|
12
34
|
}
|
|
13
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Asserts that the provided ClaspOptions include essential script project configurations.
|
|
38
|
+
* Throws an error if `scriptId`, `projectRootDir`, `configFilePath`, or `contentDir` are missing.
|
|
39
|
+
* This also acts as a type guard.
|
|
40
|
+
* @param {ClaspOptions} options - The Clasp options to check.
|
|
41
|
+
* @throws {Error} If essential script configurations are missing.
|
|
42
|
+
*/
|
|
14
43
|
export function assertScriptConfigured(options) {
|
|
15
44
|
var _a;
|
|
16
45
|
if (!((_a = options.project) === null || _a === void 0 ? void 0 : _a.scriptId) ||
|
|
@@ -25,6 +54,12 @@ export function assertScriptConfigured(options) {
|
|
|
25
54
|
});
|
|
26
55
|
}
|
|
27
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Asserts that the provided ClaspOptions include a GCP project ID, in addition to base script configurations.
|
|
59
|
+
* Throws an error if `projectId` is missing. This also acts as a type guard.
|
|
60
|
+
* @param {ClaspOptions} options - The Clasp options to check.
|
|
61
|
+
* @throws {Error} If `options.project.projectId` is not set.
|
|
62
|
+
*/
|
|
28
63
|
export function assertGcpProjectConfigured(options) {
|
|
29
64
|
var _a;
|
|
30
65
|
assertScriptConfigured(options);
|
|
@@ -74,6 +109,11 @@ export async function fetchWithPages(fn, options) {
|
|
|
74
109
|
partialResults: pageToken !== undefined,
|
|
75
110
|
};
|
|
76
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Checks if an error object is a GaxiosError with detailed error information.
|
|
114
|
+
* @param {unknown} error - The error object to check.
|
|
115
|
+
* @returns {boolean} True if the error is a GaxiosError with details, false otherwise.
|
|
116
|
+
*/
|
|
77
117
|
function isDetailedError(error) {
|
|
78
118
|
if (!error) {
|
|
79
119
|
return false;
|
|
@@ -93,13 +133,20 @@ const ERROR_CODES = {
|
|
|
93
133
|
403: 'NOT_AUTHORIZED',
|
|
94
134
|
404: 'NOT_FOUND',
|
|
95
135
|
};
|
|
136
|
+
/**
|
|
137
|
+
* Standardized error handler for Google API errors (GaxiosError).
|
|
138
|
+
* It extracts a meaningful message and error code, then re-throws a new error.
|
|
139
|
+
* @param {unknown} error - The error received from a Google API call.
|
|
140
|
+
* @throws {Error} A new error with a structured cause including the original error,
|
|
141
|
+
* a clasp-specific error code, and the message.
|
|
142
|
+
*/
|
|
96
143
|
export function handleApiError(error) {
|
|
97
144
|
var _a;
|
|
98
145
|
debug('Handling API error: %O', error);
|
|
99
146
|
if (!(error instanceof GaxiosError)) {
|
|
100
147
|
throw new Error('Unexpected error', {
|
|
101
148
|
cause: {
|
|
102
|
-
code: '
|
|
149
|
+
code: 'UNEXPECTED_ERROR',
|
|
103
150
|
message: new String(error),
|
|
104
151
|
error: error,
|
|
105
152
|
},
|
|
@@ -119,6 +166,15 @@ export function handleApiError(error) {
|
|
|
119
166
|
},
|
|
120
167
|
});
|
|
121
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Ensures that a value is an array of strings.
|
|
171
|
+
* If the input is a single string, it's wrapped in an array.
|
|
172
|
+
* If it's already an array of strings, it's returned as is.
|
|
173
|
+
* If it's an array containing non-string elements, those elements are filtered out.
|
|
174
|
+
* If the input is neither a string nor an array, an empty array is returned.
|
|
175
|
+
* @param {string | string[]} value - The value to process.
|
|
176
|
+
* @returns {string[]} An array of strings.
|
|
177
|
+
*/
|
|
122
178
|
export function ensureStringArray(value) {
|
|
123
179
|
if (typeof value === 'string') {
|
|
124
180
|
return [value];
|