@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.
Files changed (54) hide show
  1. package/README.md +35 -2
  2. package/build/src/auth/auth.js +54 -10
  3. package/build/src/auth/auth_code_flow.js +51 -0
  4. package/build/src/auth/credential_store.js +13 -0
  5. package/build/src/auth/file_credential_store.js +62 -7
  6. package/build/src/auth/localhost_auth_code_flow.js +47 -5
  7. package/build/src/auth/serverless_auth_code_flow.js +39 -2
  8. package/build/src/commands/clone-script.js +37 -5
  9. package/build/src/commands/create-deployment.js +31 -6
  10. package/build/src/commands/create-script.js +65 -24
  11. package/build/src/commands/create-version.js +21 -1
  12. package/build/src/commands/delete-deployment.js +36 -5
  13. package/build/src/commands/delete-script.js +41 -0
  14. package/build/src/commands/disable-api.js +20 -1
  15. package/build/src/commands/enable-api.js +20 -1
  16. package/build/src/commands/list-apis.js +24 -1
  17. package/build/src/commands/list-deployments.js +35 -5
  18. package/build/src/commands/list-scripts.js +26 -2
  19. package/build/src/commands/list-versions.js +35 -7
  20. package/build/src/commands/login.js +36 -10
  21. package/build/src/commands/logout.js +23 -1
  22. package/build/src/commands/open-apis.js +20 -1
  23. package/build/src/commands/open-container.js +20 -1
  24. package/build/src/commands/open-credentials.js +20 -1
  25. package/build/src/commands/open-logs.js +20 -1
  26. package/build/src/commands/open-script.js +20 -1
  27. package/build/src/commands/open-webapp.js +20 -1
  28. package/build/src/commands/program.js +48 -7
  29. package/build/src/commands/pull.js +54 -13
  30. package/build/src/commands/push.js +49 -9
  31. package/build/src/commands/run-function.js +56 -13
  32. package/build/src/commands/setup-logs.js +20 -1
  33. package/build/src/commands/show-authorized-user.js +29 -2
  34. package/build/src/commands/show-file-status.js +17 -2
  35. package/build/src/commands/start-mcp.js +17 -1
  36. package/build/src/commands/tail-logs.js +20 -5
  37. package/build/src/commands/update-deployment.js +32 -6
  38. package/build/src/commands/utils.js +68 -0
  39. package/build/src/constants.js +15 -0
  40. package/build/src/core/apis.js +13 -3
  41. package/build/src/core/clasp.js +71 -12
  42. package/build/src/core/files.js +135 -32
  43. package/build/src/core/functions.js +36 -0
  44. package/build/src/core/logs.js +29 -0
  45. package/build/src/core/manifest.js +13 -0
  46. package/build/src/core/project.js +154 -7
  47. package/build/src/core/services.js +105 -16
  48. package/build/src/core/utils.js +57 -1
  49. package/build/src/experiments.js +23 -0
  50. package/build/src/index.js +2 -0
  51. package/build/src/intl.js +28 -0
  52. package/build/src/mcp/server.js +82 -6
  53. package/docs/run.md +10 -4
  54. package/package.json +3 -3
@@ -1,3 +1,26 @@
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 functionality for managing experimental features in clasp
15
+ // through environment variables, allowing features to be toggled on or off.
16
+ /**
17
+ * Checks if an experimental feature is enabled via environment variables.
18
+ * The environment variable is expected to be in the format `CLASP_EXPERIMENT_NAME_IN_UPPERCASE`.
19
+ * Truthy values are 'true' (case-insensitive) or '1'.
20
+ * @param {string} experimentName - The name of the experiment (e.g., "enable_user_hints").
21
+ * @param {boolean} [defaultValue=false] - The default value if the environment variable is not set.
22
+ * @returns {boolean} True if the experiment is enabled, false otherwise.
23
+ */
1
24
  export function isEnabled(experimentName, defaultValue = false) {
2
25
  const envVarName = `CLASP_${experimentName.toUpperCase()}`;
3
26
  const envVarValue = process.env[envVarName];
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ // This file is the main entry point for the clasp CLI. It sets up the command
3
+ // parsing, executes the appropriate command, and handles top-level errors.
2
4
  /**
3
5
  * @license
4
6
  * Copyright Google Inc.
package/build/src/intl.js CHANGED
@@ -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 sets up internationalization (i18n) for the clasp CLI using
15
+ // @formatjs/intl. It detects the user's locale and provides an `intl`
16
+ // object for formatting localized messages.
1
17
  import { createIntl, createIntlCache } from '@formatjs/intl';
2
18
  import Debug from 'debug';
3
19
  const debug = Debug('clasp:intl');
@@ -27,6 +43,18 @@ const cache = createIntlCache();
27
43
  const locale = getLocale();
28
44
  const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
29
45
  debug('Using locale: %s', locale);
46
+ /**
47
+ * Internationalization instance configured with the user's detected locale
48
+ * (or 'en' as default). This object is used for formatting localized messages,
49
+ * dates, numbers, etc., throughout the application.
50
+ * It is created using `@formatjs/intl`.
51
+ *
52
+ * Example usage:
53
+ * ```
54
+ * import {intl} from './intl.js';
55
+ * const message = intl.formatMessage({defaultMessage: "Hello, world!"});
56
+ * ```
57
+ */
30
58
  export const intl = createIntl({
31
59
  // Locale of the application
32
60
  locale,
@@ -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 builds and configures a Model Context Protocol (MCP) server
15
+ // that exposes clasp functionalities (like push, pull, create, clone, list)
16
+ // as remotely callable tools for programmatic interaction.
1
17
  import path from 'path';
2
18
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
19
  import { mkdir } from 'fs/promises';
@@ -5,6 +21,24 @@ import { z } from 'zod';
5
21
  import { getDefaultProjectName } from '../commands/create-script.js';
6
22
  import { getVersion } from '../commands/program.js';
7
23
  import { initClaspInstance } from '../core/clasp.js';
24
+ /**
25
+ * Builds and configures an MCP (Model Context Protocol) server with tools
26
+ * for interacting with Google Apps Script projects via clasp functionalities.
27
+ *
28
+ * The server exposes tools such as:
29
+ * - `push_files`: Pushes local project files to the remote Apps Script project.
30
+ * - `pull_files`: Pulls remote Apps Script project files to the local filesystem.
31
+ * - `create_project`: Creates a new Apps Script project.
32
+ * - `clone_project`: Clones an existing Apps Script project to a local directory.
33
+ * - `list_projects`: Lists Apps Script projects accessible to the authenticated user.
34
+ *
35
+ * Each tool is defined with a description, input schema (using Zod),
36
+ * and an asynchronous handler that executes the corresponding clasp logic.
37
+ *
38
+ * @param {AuthInfo} auth - Authentication information containing the OAuth2 credentials
39
+ * required by clasp to interact with Google APIs.
40
+ * @returns {McpServer} The configured MCP server instance.
41
+ */
8
42
  export function buildMcpServer(auth) {
9
43
  const server = new McpServer({
10
44
  name: 'Clasp',
@@ -21,6 +55,7 @@ export function buildMcpServer(auth) {
21
55
  idempotentHint: false,
22
56
  readOnlyHint: false,
23
57
  }, async ({ projectDir }) => {
58
+ // Validate required input.
24
59
  if (!projectDir) {
25
60
  return {
26
61
  isError: true,
@@ -32,20 +67,24 @@ export function buildMcpServer(auth) {
32
67
  ],
33
68
  };
34
69
  }
70
+ // Initialize a Clasp instance scoped to the provided project directory.
35
71
  const clasp = await initClaspInstance({
36
72
  credentials: auth.credentials,
37
- configFile: projectDir,
38
- rootDir: projectDir,
73
+ configFile: projectDir, // Tells initClaspInstance to look for .clasp.json in this dir.
74
+ rootDir: projectDir, // Fallback root if .clasp.json isn't immediately found.
39
75
  });
40
76
  try {
77
+ // Execute the push operation.
41
78
  const files = await clasp.files.push();
79
+ // Format the list of pushed files for the MCP response.
42
80
  const fileList = files.map(file => ({
43
81
  type: 'text',
44
82
  text: `Updated file: ${path.resolve(file.localPath)}`,
45
83
  }));
46
84
  return {
47
- status: 'success',
85
+ status: 'success', // Indicate successful execution.
48
86
  content: [
87
+ // Human-readable output.
49
88
  {
50
89
  type: 'text',
51
90
  text: `Pushed project in ${projectDir} to remote server successfully.`,
@@ -53,6 +92,7 @@ export function buildMcpServer(auth) {
53
92
  ...fileList,
54
93
  ],
55
94
  structuredContent: {
95
+ // Machine-readable output.
56
96
  scriptId: clasp.project.scriptId,
57
97
  projectDir: projectDir,
58
98
  files: files.map(file => path.resolve(file.localPath)),
@@ -60,6 +100,7 @@ export function buildMcpServer(auth) {
60
100
  };
61
101
  }
62
102
  catch (err) {
103
+ // Handle errors during the push operation.
63
104
  return {
64
105
  isError: true,
65
106
  content: [
@@ -82,6 +123,7 @@ export function buildMcpServer(auth) {
82
123
  idempotentHint: false,
83
124
  readOnlyHint: false,
84
125
  }, async ({ projectDir }) => {
126
+ // Validate required input.
85
127
  if (!projectDir) {
86
128
  return {
87
129
  isError: true,
@@ -93,19 +135,23 @@ export function buildMcpServer(auth) {
93
135
  ],
94
136
  };
95
137
  }
138
+ // Initialize a Clasp instance for the specified project directory.
96
139
  const clasp = await initClaspInstance({
97
140
  credentials: auth.credentials,
98
141
  configFile: projectDir,
99
142
  rootDir: projectDir,
100
143
  });
101
144
  try {
145
+ // Execute the pull operation.
102
146
  const files = await clasp.files.pull();
147
+ // Format the list of pulled files for the MCP response.
103
148
  const fileList = files.map(file => ({
104
149
  type: 'text',
105
150
  text: `Updated file: ${path.resolve(file.localPath)}`,
106
151
  }));
107
152
  return {
108
153
  content: [
154
+ // Human-readable output.
109
155
  {
110
156
  type: 'text',
111
157
  text: `Pushed project in ${projectDir} to remote server successfully.`,
@@ -113,6 +159,7 @@ export function buildMcpServer(auth) {
113
159
  ...fileList,
114
160
  ],
115
161
  structuredContent: {
162
+ // Machine-readable output.
116
163
  scriptId: clasp.project.scriptId,
117
164
  projectDir: projectDir,
118
165
  files: files.map(file => path.resolve(file.localPath)),
@@ -120,6 +167,7 @@ export function buildMcpServer(auth) {
120
167
  };
121
168
  }
122
169
  catch (err) {
170
+ // Handle errors during the pull operation.
123
171
  return {
124
172
  isError: true,
125
173
  content: [
@@ -148,6 +196,7 @@ export function buildMcpServer(auth) {
148
196
  idempotentHint: false,
149
197
  readOnlyHint: false,
150
198
  }, async ({ projectDir, sourceDir, projectName }) => {
199
+ // Validate required input.
151
200
  if (!projectDir) {
152
201
  return {
153
202
  isError: true,
@@ -159,19 +208,27 @@ export function buildMcpServer(auth) {
159
208
  ],
160
209
  };
161
210
  }
211
+ // Ensure the project directory exists.
162
212
  await mkdir(projectDir, { recursive: true });
213
+ // If projectName is not provided, infer it from the project directory name.
163
214
  if (!projectName) {
164
215
  projectName = getDefaultProjectName(projectDir);
165
216
  }
217
+ // Initialize a Clasp instance. Since it's a new project,
218
+ // .clasp.json might not exist yet, so rootDir helps locate where it would be.
166
219
  const clasp = await initClaspInstance({
167
220
  credentials: auth.credentials,
168
- configFile: projectDir,
221
+ configFile: projectDir, // Will look for .clasp.json here or create it.
169
222
  rootDir: projectDir,
170
223
  });
171
- clasp.withContentDir(sourceDir !== null && sourceDir !== void 0 ? sourceDir : '.');
224
+ // Set the content directory (where .js, .html files will go) if specified.
225
+ clasp.withContentDir(sourceDir !== null && sourceDir !== void 0 ? sourceDir : '.'); // Defaults to projectDir if sourceDir is not given.
172
226
  try {
227
+ // Create the new Apps Script project remotely.
173
228
  const id = await clasp.project.createScript(projectName);
229
+ // Pull the initial files (e.g., appsscript.json, Code.js) from the new project.
174
230
  const files = await clasp.files.pull();
231
+ // Write the .clasp.json file with the new script ID and other settings.
175
232
  await clasp.project.updateSettings();
176
233
  const fileList = files.map(file => ({
177
234
  type: 'text',
@@ -179,6 +236,7 @@ export function buildMcpServer(auth) {
179
236
  }));
180
237
  return {
181
238
  content: [
239
+ // Human-readable output.
182
240
  {
183
241
  type: 'text',
184
242
  text: `Created project ${id} in ${projectDir} successfully.`,
@@ -186,6 +244,7 @@ export function buildMcpServer(auth) {
186
244
  ...fileList,
187
245
  ],
188
246
  structuredContent: {
247
+ // Machine-readable output.
189
248
  scriptId: id,
190
249
  projectDir: projectDir,
191
250
  files: files.map(file => path.resolve(file.localPath)),
@@ -193,6 +252,7 @@ export function buildMcpServer(auth) {
193
252
  };
194
253
  }
195
254
  catch (err) {
255
+ // Handle errors during project creation or initial pull.
196
256
  return {
197
257
  isError: true,
198
258
  content: [
@@ -218,6 +278,7 @@ export function buildMcpServer(auth) {
218
278
  idempotentHint: false,
219
279
  readOnlyHint: false,
220
280
  }, async ({ projectDir, sourceDir, scriptId }) => {
281
+ // Validate required inputs.
221
282
  if (!projectDir) {
222
283
  return {
223
284
  isError: true,
@@ -229,8 +290,10 @@ export function buildMcpServer(auth) {
229
290
  ],
230
291
  };
231
292
  }
293
+ // Ensure the local project directory exists.
232
294
  await mkdir(projectDir, { recursive: true });
233
295
  if (!scriptId) {
296
+ // Script ID is essential for cloning.
234
297
  return {
235
298
  isError: true,
236
299
  content: [
@@ -241,14 +304,18 @@ export function buildMcpServer(auth) {
241
304
  ],
242
305
  };
243
306
  }
307
+ // Initialize Clasp instance for the new local project directory.
244
308
  const clasp = await initClaspInstance({
245
309
  credentials: auth.credentials,
246
310
  configFile: projectDir,
247
311
  rootDir: projectDir,
248
312
  });
313
+ // Configure the Clasp instance with the target script ID and content directory.
249
314
  clasp.withContentDir(sourceDir !== null && sourceDir !== void 0 ? sourceDir : '.').withScriptId(scriptId);
250
315
  try {
316
+ // Pull files from the specified remote script ID.
251
317
  const files = await clasp.files.pull();
318
+ // Create/update the .clasp.json file with the cloned script's ID and settings.
252
319
  clasp.project.updateSettings();
253
320
  const fileList = files.map(file => ({
254
321
  type: 'text',
@@ -256,6 +323,7 @@ export function buildMcpServer(auth) {
256
323
  }));
257
324
  return {
258
325
  content: [
326
+ // Human-readable output.
259
327
  {
260
328
  type: 'text',
261
329
  text: `Cloned project ${scriptId} in ${projectDir} successfully.`,
@@ -263,6 +331,7 @@ export function buildMcpServer(auth) {
263
331
  ...fileList,
264
332
  ],
265
333
  structuredContent: {
334
+ // Machine-readable output.
266
335
  scriptId: scriptId,
267
336
  projectDir: projectDir,
268
337
  files: files.map(file => path.resolve(file.localPath)),
@@ -270,6 +339,7 @@ export function buildMcpServer(auth) {
270
339
  };
271
340
  }
272
341
  catch (err) {
342
+ // Handle errors during cloning.
273
343
  return {
274
344
  isError: true,
275
345
  content: [
@@ -288,17 +358,21 @@ export function buildMcpServer(auth) {
288
358
  idempotentHint: false,
289
359
  readOnlyHint: false,
290
360
  }, async () => {
361
+ // Initialize a Clasp instance (doesn't need a specific project directory for listing).
291
362
  const clasp = await initClaspInstance({
292
363
  credentials: auth.credentials,
293
364
  });
294
365
  try {
366
+ // Fetch the list of scripts.
295
367
  const scripts = await clasp.project.listScripts();
368
+ // Format the script list for the MCP response.
296
369
  const scriptList = scripts.results.map(script => ({
297
370
  type: 'text',
298
- text: `${script.name} (${script.id})`,
371
+ text: `${script.name} (${script.id})`, // Display name and ID.
299
372
  }));
300
373
  return {
301
374
  content: [
375
+ // Human-readable output.
302
376
  {
303
377
  type: 'text',
304
378
  text: `Found ${scripts.results.length} Apps Script projects (script ID in parentheses):`,
@@ -306,6 +380,7 @@ export function buildMcpServer(auth) {
306
380
  ...scriptList,
307
381
  ],
308
382
  structuredContent: {
383
+ // Machine-readable output.
309
384
  scripts: scripts.results.map(script => ({
310
385
  scriptId: script.id,
311
386
  name: script.name,
@@ -314,6 +389,7 @@ export function buildMcpServer(auth) {
314
389
  };
315
390
  }
316
391
  catch (err) {
392
+ // Handle errors during listing.
317
393
  return {
318
394
  isError: true,
319
395
  content: [
package/docs/run.md CHANGED
@@ -24,11 +24,17 @@ To use `clasp run`, you need to complete 5 steps:
24
24
  1. Add a `projectId` to your `.clasp.json`. You can find your Project ID via:
25
25
  - [Create a GCP project](https://cloud.google.com/resource-manager/docs/creating-managing-projects)
26
26
  - Record the `Project ID` and `Project number`. (Example: `my-sample-project-191923.` and `314053285323`)
27
- - Run the command with your `Project ID`: `clasp setting projectId <PROJECT_ID>`
27
+ - Manually add the `Project ID` to your `.clasp.json` file.
28
+ ```json
29
+ {
30
+ "scriptId": "...",
31
+ "projectId": "my-sample-project-191923"
32
+ }
33
+ ```
28
34
  1. Set the `projectId` to your Apps Script project
29
35
  - Open `https://console.developers.google.com/apis/credentials/consent?project=[PROJECT_ID]`
30
36
  - Set `Application name` to `clasp project` and click `save`.
31
- - Run `clasp open`
37
+ - Run `clasp open-script`
32
38
  - In the menu, click `⚙️ Project Settings > Google Cloud Platform (GCP) Project`
33
39
  - If the `Project Number` is missing,
34
40
  - Click `Change Project`, paste the PROJECT_NUMBER, and click `Set project`
@@ -61,7 +67,7 @@ After setup, you can remotely execute Apps Script functions from `clasp`:
61
67
 
62
68
  If you get an "Script API executable not published/deployed." error, deploy your script as an API Executable:
63
69
 
64
- - Run `clasp open`
70
+ - Run `clasp open-script`
65
71
  - Click `Deploy > New deployment`
66
72
  - Select type ⚙ > API Executable
67
73
  - Type a `Description`
@@ -73,7 +79,7 @@ Many Apps Script functions require special OAuth Scopes (Gmail, Drive, etc.).
73
79
 
74
80
  To run functions that use these scopes, you must add the scopes to your Apps Script manifest and `clasp login` again.
75
81
 
76
- - `clasp open`
82
+ - `clasp open-script`
77
83
  - `File > Project Properties > Scopes`
78
84
  - Add these [scopes to your `appsscript.json`](https://developers.google.com/apps-script/concepts/scopes#set-explicit).
79
85
  - Log in again: `clasp login --user <name> --use-project-scopes --creds creds.json`. This will add these scopes to your credentials.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@google/clasp",
3
- "version": "3.0.6-alpha",
3
+ "version": "3.1.1",
4
4
  "description": "Develop Apps Script Projects locally",
5
5
  "type": "module",
6
6
  "exports": "./build/src/index.js",
@@ -22,7 +22,7 @@
22
22
  "prepare": "npm run compile",
23
23
  "lint": "npm run check",
24
24
  "test": "mocha",
25
- "test:coverage": "c8 mocha",
25
+ "test:coverage": "c8 --reports-dir coverage --reporter=text --reporter=html --reporter=lcov mocha",
26
26
  "prettier": "biome format src test --write",
27
27
  "check": "biome check src test && npm run compile",
28
28
  "clean": "rm -rf build",
@@ -56,6 +56,7 @@
56
56
  "license": "Apache-2.0",
57
57
  "dependencies": {
58
58
  "@formatjs/intl": "^3.1.6",
59
+ "@modelcontextprotocol/sdk": "^1.12.1",
59
60
  "chalk": "^5.4.1",
60
61
  "chokidar": "^4.0.3",
61
62
  "cli-truncate": "^4.0.0",
@@ -72,7 +73,6 @@
72
73
  "inquirer-autocomplete-standalone": "^0.8.1",
73
74
  "loud-rejection": "^2.2.0",
74
75
  "micromatch": "^4.0.8",
75
- "@modelcontextprotocol/sdk": "^1.12.1",
76
76
  "normalize-path": "^3.0.0",
77
77
  "open": "^10.1.2",
78
78
  "ora": "^8.1.1",