@google/clasp 3.0.6-alpha → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +21 -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 +35 -0
  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 +8 -2
  54. package/package.json +3 -3
@@ -1,3 +1,17 @@
1
+ // Copyright 2019 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 'push' command for the clasp CLI.
1
15
  import path from 'path';
2
16
  import { Command } from 'commander';
3
17
  import inquirer from 'inquirer';
@@ -7,48 +21,74 @@ export const command = new Command('push')
7
21
  .description('Update the remote project')
8
22
  .option('-f, --force', 'Forcibly overwrites the remote manifest.')
9
23
  .option('-w, --watch', 'Watches for local file changes. Pushes when a non-ignored file changes.')
10
- .action(async function (options) {
11
- const clasp = this.opts().clasp;
24
+ .action(async function () {
25
+ const options = this.optsWithGlobals();
26
+ const clasp = options.clasp;
12
27
  const watch = options.watch;
13
- let force = options.force;
28
+ let force = options.force; // Store the force option, as it can be updated by confirmManifestUpdate
29
+ // Defines the action to take when a file change is detected (either initially or during watch mode).
14
30
  const onChange = async (paths) => {
31
+ // Check if the manifest file (appsscript.json) is among the changed files.
15
32
  const isManifestUpdated = paths.findIndex(p => path.basename(p) === 'appsscript.json') !== -1;
33
+ // If the manifest is updated and not using --force, prompt the user for confirmation.
16
34
  if (isManifestUpdated && !force) {
17
- force = await confirmManifestUpdate();
35
+ force = await confirmManifestUpdate(); // Update force based on user's choice.
18
36
  if (!force) {
19
- const msg = intl.formatMessage({ id: "TItFfu", defaultMessage: [{ type: 0, value: "Skipping push." }] });
20
- console.log(msg);
21
- return;
37
+ // If user declines manifest overwrite, skip the push.
38
+ if (!options.json) {
39
+ const msg = intl.formatMessage({ id: "TItFfu", defaultMessage: [{ type: 0, value: "Skipping push." }] });
40
+ console.log(msg);
41
+ }
42
+ return; // Exit onChange without pushing.
22
43
  }
23
44
  }
24
45
  const spinnerMsg = intl.formatMessage({ id: "qUq++d", defaultMessage: [{ type: 0, value: "Pushing files..." }] });
46
+ // Perform the push operation using the core `clasp.files.push()` method.
25
47
  const files = await withSpinner(spinnerMsg, async () => {
26
- return await clasp.files.push();
48
+ return clasp.files.push();
27
49
  });
50
+ if (options.json) {
51
+ console.log(JSON.stringify(files.map(f => f.localPath), null, 2));
52
+ return;
53
+ }
54
+ // Log the result of the push.
28
55
  const successMessage = intl.formatMessage({ id: "aD3XSt", defaultMessage: [{ type: 0, value: "Pushed " }, { type: 6, value: "count", options: { "=0": { value: [{ type: 0, value: "no files." }] }, one: { value: [{ type: 0, value: "one file." }] }, other: { value: [{ type: 7 }, { type: 0, value: " files" }] } }, offset: 0, pluralType: "cardinal" }, { type: 0, value: "." }] }, {
29
56
  count: files.length,
30
57
  });
31
58
  console.log(successMessage);
32
59
  files.forEach(f => console.log(`└─ ${f.localPath}`));
33
- return true;
60
+ return true; // Indicate that the push was attempted (or successfully skipped by user).
34
61
  };
62
+ // Initial check for pending changes when the command is first run.
35
63
  const pendingChanges = await clasp.files.getChangedFiles();
36
64
  if (pendingChanges.length) {
65
+ // If there are changes, map them to their paths and call onChange.
37
66
  const paths = pendingChanges.map(f => f.localPath);
38
67
  await onChange(paths);
39
68
  }
40
69
  else {
70
+ if (options.json) {
71
+ console.log(JSON.stringify([], null, 2));
72
+ return;
73
+ }
74
+ // If no changes, inform the user.
41
75
  const msg = intl.formatMessage({ id: "X/QgBZ", defaultMessage: [{ type: 0, value: "Script is already up to date." }] });
42
76
  console.log(msg);
43
77
  }
78
+ // If not in watch mode, exit after the initial push attempt.
44
79
  if (!watch) {
45
80
  return;
46
81
  }
82
+ // Setup for watch mode.
47
83
  const onReady = async () => {
48
84
  const msg = intl.formatMessage({ id: "m/C0lF", defaultMessage: [{ type: 0, value: "Waiting for changes..." }] });
49
85
  console.log(msg);
50
86
  };
87
+ // Start watching local files. The `onChange` function will be called on subsequent changes.
88
+ // `watchLocalFiles` returns a function to stop the watcher.
51
89
  const stopWatching = await clasp.files.watchLocalFiles(onReady, async (paths) => {
90
+ // If onChange returns undefined (e.g. user skipped manifest push), it implies we should stop watching.
91
+ // This can happen if the user cancels the manifest push in interactive mode.
52
92
  if (!(await onChange(paths))) {
53
93
  stopWatching();
54
94
  }
@@ -1,3 +1,17 @@
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 'run-function' (alias 'run') command for the clasp CLI.
1
15
  import chalk from 'chalk';
2
16
  import { Command } from 'commander';
3
17
  import fuzzy from 'fuzzy';
@@ -10,52 +24,81 @@ export const command = new Command('run-function')
10
24
  .argument('[functionName]', 'The name of the function to run')
11
25
  .option('--nondev', 'Run script function in non-devMode')
12
26
  .option('-p, --params <value>', 'Parameters to pass to the function, as a JSON-encoded array')
13
- .action(async function (functionName, options) {
14
- var _a, _b;
15
- const clasp = this.opts().clasp;
27
+ .action(async function (functionName) {
28
+ var _a, _b, _c;
29
+ const options = this.optsWithGlobals();
30
+ const clasp = options.clasp;
16
31
  const devMode = !options.nondev; // Defaults to true
17
32
  let params = [];
18
33
  if (options.params) {
34
+ // Parameters for the function are expected to be a JSON-encoded array.
19
35
  params = JSON.parse(options.params);
20
36
  }
37
+ // If no function name is provided and the session is interactive,
38
+ // fetch all function names from the project and prompt the user to select one.
21
39
  if (!functionName && isInteractive()) {
22
40
  const allFunctions = await clasp.functions.getFunctionNames();
41
+ // `inquirer-autocomplete-standalone` provides a fuzzy-searchable list.
23
42
  const source = async (input = '') => fuzzy.filter(input, allFunctions).map(element => ({
24
- value: element.original,
43
+ value: element.original, // The original function name is the value.
25
44
  }));
26
45
  const prompt = intl.formatMessage({ id: "Y8u3Vb", defaultMessage: [{ type: 0, value: "Selection a function name" }] });
27
46
  functionName = await autocomplete({
28
47
  message: prompt,
29
- source,
48
+ source, // Source function for the autocomplete.
30
49
  });
31
50
  }
51
+ // Attempt to run the function.
32
52
  try {
33
- const { error, response } = await withSpinner(`Running function: ${functionName}`, async () => {
34
- return await clasp.functions.runFunction(functionName, params, devMode);
53
+ // `clasp.functions.runFunction` calls the Apps Script API.
54
+ const result = await withSpinner(`Running function: ${functionName}`, async () => {
55
+ return clasp.functions.runFunction(functionName, params, devMode);
35
56
  });
36
- if (error && error.details) {
37
- const { errorMessage, scriptStackTraceElements } = error.details[0];
57
+ if (options.json) {
58
+ const output = {
59
+ response: (_a = result.response) === null || _a === void 0 ? void 0 : _a.result,
60
+ error: result.error
61
+ ? {
62
+ code: result.error.code,
63
+ message: result.error.message,
64
+ details: result.error.details,
65
+ }
66
+ : undefined,
67
+ };
68
+ console.log(JSON.stringify(output, null, 2));
69
+ return;
70
+ }
71
+ // Handle the API response.
72
+ if (result.error && result.error.details) {
73
+ // If the API returned an error in the `error.details` field (common for script execution errors).
74
+ const { errorMessage, scriptStackTraceElements } = result.error.details[0];
38
75
  const msg = intl.formatMessage({ id: "1l462L", defaultMessage: [{ type: 0, value: "Exception:" }] });
39
76
  console.error(`${chalk.red(msg)}`, errorMessage, scriptStackTraceElements || []);
40
77
  return;
41
78
  }
42
- if (response && response.result !== undefined) {
43
- console.log(response.result);
79
+ if (result.response && result.response.result !== undefined) {
80
+ // If the function executed successfully and returned a result.
81
+ console.log(result.response.result);
44
82
  }
45
83
  else {
84
+ // If the function execution didn't produce a result or an error in the expected format.
46
85
  const msg = intl.formatMessage({ id: "S/0IKk", defaultMessage: [{ type: 0, value: "No response." }] });
47
86
  console.log(chalk.red(msg));
48
87
  }
49
88
  }
50
89
  catch (error) {
51
- if (((_a = error.cause) === null || _a === void 0 ? void 0 : _a.code) === 'NOT_AUTHORIZED') {
90
+ // Handle errors thrown by `clasp.functions.runFunction` or other issues.
91
+ if (((_b = error.cause) === null || _b === void 0 ? void 0 : _b.code) === 'NOT_AUTHORIZED') {
92
+ // Specific error for lack of permissions.
52
93
  const msg = intl.formatMessage({ id: "HZmND2", defaultMessage: [{ type: 0, value: "Unable to run script function. Please make sure you have permission to run the script function." }] });
53
94
  this.error(msg);
54
95
  }
55
- if (((_b = error.cause) === null || _b === void 0 ? void 0 : _b.code) === 'NOT_FOUND') {
96
+ if (((_c = error.cause) === null || _c === void 0 ? void 0 : _c.code) === 'NOT_FOUND') {
97
+ // Specific error if the function or script (as API executable) is not found.
56
98
  const msg = intl.formatMessage({ id: "4wxpit", defaultMessage: [{ type: 0, value: "Script function not found. Please make sure script is deployed as API executable." }] });
57
99
  this.error(msg);
58
100
  }
101
+ // Re-throw other errors to be caught by the global error handler.
59
102
  throw error;
60
103
  }
61
104
  });
@@ -1,12 +1,31 @@
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 'setup-logs' command for the clasp CLI.
1
15
  import { Command } from 'commander';
2
16
  import { intl } from '../intl.js';
3
17
  import { assertGcpProjectConfigured, isInteractive, maybePromptForProjectId } from './utils.js';
4
18
  export const command = new Command('setup-logs').description('Setup Cloud Logging').action(async function () {
5
- const clasp = this.opts().clasp;
19
+ const options = this.optsWithGlobals();
20
+ const clasp = options.clasp;
6
21
  if (!clasp.project.projectId && isInteractive()) {
7
22
  await maybePromptForProjectId(clasp);
8
23
  }
9
24
  assertGcpProjectConfigured(clasp);
25
+ if (options.json) {
26
+ console.log(JSON.stringify({ success: true }, null, 2));
27
+ return;
28
+ }
10
29
  const successMessage = intl.formatMessage({ id: "mrsf6I", defaultMessage: [{ type: 0, value: "Script logs are now available in Cloud Logging." }] });
11
30
  console.log(successMessage);
12
31
  });
@@ -1,16 +1,43 @@
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 'show-authorized-user' command for the clasp CLI.
1
15
  import { Command } from 'commander';
2
16
  import { getUserInfo } from '../auth/auth.js';
3
17
  import { intl } from '../intl.js';
4
18
  export const command = new Command('show-authorized-user')
5
19
  .description('Show information about the current authorizations state.')
6
20
  .action(async function () {
7
- const auth = this.opts().auth;
21
+ var _a;
22
+ const options = this.optsWithGlobals();
23
+ const auth = options.authInfo;
24
+ let user = undefined;
25
+ if (auth.credentials) {
26
+ user = await getUserInfo(auth.credentials);
27
+ }
28
+ if (options.json) {
29
+ const output = {
30
+ loggedIn: auth.credentials ? true : false,
31
+ email: (_a = user === null || user === void 0 ? void 0 : user.email) !== null && _a !== void 0 ? _a : undefined,
32
+ };
33
+ console.log(JSON.stringify(output, null, 2));
34
+ return;
35
+ }
8
36
  if (!auth.credentials) {
9
37
  const msg = intl.formatMessage({ id: "ZqMsgV", defaultMessage: [{ type: 0, value: "Not logged in." }] });
10
38
  console.log(msg);
11
39
  return;
12
40
  }
13
- const user = await getUserInfo(auth.credentials);
14
41
  const msg = intl.formatMessage({ id: "sZ9k34", defaultMessage: [{ type: 5, value: "email", options: { undefined: { value: [{ type: 0, value: "You are logged in as an unknown user." }] }, other: { value: [{ type: 0, value: "You are logged in as " }, { type: 1, value: "email" }, { type: 0, value: "." }] } } }] }, {
15
42
  email: user === null || user === void 0 ? void 0 : user.email,
16
43
  });
@@ -1,12 +1,27 @@
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 'show-file-status' (alias 'status') command for the
15
+ // clasp CLI.
1
16
  import { Command } from 'commander';
2
17
  import { intl } from '../intl.js';
3
18
  import { withSpinner } from './utils.js';
4
19
  export const command = new Command('show-file-status')
5
20
  .alias('status')
6
21
  .description('Lists files that will be pushed by clasp')
7
- .option('--json', 'Show status in JSON form')
8
- .action(async function (options) {
22
+ .action(async function () {
9
23
  var _a;
24
+ const options = this.optsWithGlobals();
10
25
  const clasp = this.opts().clasp;
11
26
  const outputAsJson = (_a = options === null || options === void 0 ? void 0 : options.json) !== null && _a !== void 0 ? _a : false;
12
27
  const spinnerMsg = intl.formatMessage({ id: "3pOneN", defaultMessage: [{ type: 0, value: "Analyzing project files..." }] });
@@ -1,3 +1,18 @@
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 'start-mcp-server' (alias 'mcp') command for the
15
+ // clasp CLI.
1
16
  import { Command } from 'commander';
2
17
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
18
  import { buildMcpServer } from '../mcp/server.js';
@@ -5,7 +20,8 @@ export const command = new Command('start-mcp-server')
5
20
  .alias('mcp')
6
21
  .description('Starts an MCP server for interacting with apps script.')
7
22
  .action(async function () {
8
- const auth = this.opts().auth;
23
+ const options = this.optsWithGlobals();
24
+ const auth = options.authInfo;
9
25
  const server = buildMcpServer(auth);
10
26
  const transport = new StdioServerTransport();
11
27
  await server.connect(transport);
@@ -1,15 +1,29 @@
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 'tail-logs' (alias 'logs') command for the clasp CLI.
1
15
  import chalk from 'chalk';
2
16
  import { Command } from 'commander';
3
17
  import { intl } from '../intl.js';
4
- import { assertGcpProjectConfigured, isInteractive, maybePromptForProjectId, withSpinner } from './utils.js';
18
+ import { assertGcpProjectConfigured, isInteractive, maybePromptForProjectId, withSpinner, } from './utils.js';
5
19
  export const command = new Command('tail-logs')
6
20
  .alias('logs')
7
21
  .description('Print the most recent log entries')
8
- .option('--json', 'Show logs in JSON form')
9
22
  .option('--watch', 'Watch and print new logs')
10
23
  .option('--simplified', 'Hide timestamps with logs')
11
- .action(async function (options) {
12
- const clasp = this.opts().clasp;
24
+ .action(async function () {
25
+ const options = this.optsWithGlobals();
26
+ const clasp = options.clasp;
13
27
  const { json, simplified, watch } = options;
14
28
  const seenEntries = new Set();
15
29
  let since;
@@ -86,7 +100,8 @@ function formatEntry(entry, options) {
86
100
  return undefined;
87
101
  }
88
102
  }
89
- const coloredSeverity = `${severityColor[severity](severity) || severity}`.padEnd(20);
103
+ const colorizer = severityColor[severity];
104
+ const coloredSeverity = `${colorizer ? colorizer(severity) : severity}`.padEnd(20);
90
105
  functionName = functionName.padEnd(15);
91
106
  payloadData = payloadData.padEnd(20);
92
107
  const localizedTime = getLocalISODateTime(new Date(timestamp));
@@ -1,3 +1,18 @@
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 'update-deployment' (alias 'redeploy') command for
15
+ // the clasp CLI.
1
16
  import { Command } from 'commander';
2
17
  import { intl } from '../intl.js';
3
18
  import { withSpinner } from './utils.js';
@@ -7,9 +22,11 @@ export const command = new Command('update-deployment')
7
22
  .description('Updates a deployment for a project to a new version')
8
23
  .option('-V, --versionNumber <version>', 'The project version')
9
24
  .option('-d, --description <description>', 'The deployment description')
10
- .action(async function (deploymentId, options) {
11
- var _a, _b, _c;
12
- const clasp = this.opts().clasp;
25
+ .option('--json', 'Show output in JSON format')
26
+ .action(async function (deploymentId) {
27
+ var _a, _b, _c, _d, _e;
28
+ const options = this.optsWithGlobals();
29
+ const clasp = options.clasp;
13
30
  const description = (_a = options.description) !== null && _a !== void 0 ? _a : '';
14
31
  const versionNumber = options.versionNumber ? Number(options.versionNumber) : undefined;
15
32
  if (!deploymentId) {
@@ -19,16 +36,25 @@ export const command = new Command('update-deployment')
19
36
  try {
20
37
  const spinnerMsg = intl.formatMessage({ id: "oL8t7p", defaultMessage: [{ type: 0, value: "Deploying project..." }] });
21
38
  const deployment = await withSpinner(spinnerMsg, async () => {
22
- return await clasp.project.deploy(description, deploymentId, versionNumber);
39
+ return clasp.project.deploy(description, deploymentId, versionNumber);
23
40
  });
41
+ if (options.json) {
42
+ const output = {
43
+ deploymentId: deployment.deploymentId,
44
+ versionNumber: (_b = deployment.deploymentConfig) === null || _b === void 0 ? void 0 : _b.versionNumber,
45
+ description: (_c = deployment.deploymentConfig) === null || _c === void 0 ? void 0 : _c.description,
46
+ };
47
+ console.log(JSON.stringify(output, null, 2));
48
+ return;
49
+ }
24
50
  const successMessage = intl.formatMessage({ id: "wWBE7L", defaultMessage: [{ type: 0, value: "Redeployed " }, { type: 1, value: "deploymentId" }, { type: 0, value: " " }, { type: 5, value: "version", options: { undefined: { value: [{ type: 0, value: "@HEAD" }] }, other: { value: [{ type: 0, value: "@" }, { type: 1, value: "version" }] } } }] }, {
25
51
  deploymentId: deployment.deploymentId,
26
- version: (_b = deployment.deploymentConfig) === null || _b === void 0 ? void 0 : _b.versionNumber,
52
+ version: (_d = deployment.deploymentConfig) === null || _d === void 0 ? void 0 : _d.versionNumber,
27
53
  });
28
54
  console.log(successMessage);
29
55
  }
30
56
  catch (error) {
31
- if (((_c = error.cause) === null || _c === void 0 ? void 0 : _c.code) === 'INVALID_ARGUMENT') {
57
+ if (((_e = error.cause) === null || _e === void 0 ? void 0 : _e.code) === 'INVALID_ARGUMENT') {
32
58
  this.error(error.cause.message);
33
59
  }
34
60
  throw error;
@@ -1,8 +1,29 @@
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 contains utility functions for the clasp CLI commands, such as
15
+ // spinners, URL opening, and configuration assertions.
1
16
  import cliTruncate from 'cli-truncate';
2
17
  import inquirer from 'inquirer';
3
18
  import open from 'open';
4
19
  import ora from 'ora';
5
20
  import { intl } from '../intl.js';
21
+ /**
22
+ * Asserts that a script project is configured by checking for a script ID.
23
+ * Throws an error if the script ID is not set.
24
+ * @param {Clasp} clasp - The Clasp instance containing project details.
25
+ * @throws {Error} If `clasp.project.scriptId` is not set.
26
+ */
6
27
  export function assertScriptConfigured(clasp) {
7
28
  if (clasp.project.scriptId) {
8
29
  return;
@@ -10,6 +31,12 @@ export function assertScriptConfigured(clasp) {
10
31
  const msg = intl.formatMessage({ id: "2IuvqO", defaultMessage: [{ type: 0, value: "Script ID is not set, unable to continue." }] });
11
32
  throw new Error(msg);
12
33
  }
34
+ /**
35
+ * Asserts that a Google Cloud Platform (GCP) project is configured by checking for a project ID.
36
+ * Throws an error if the GCP project ID is not set.
37
+ * @param {Clasp} clasp - The Clasp instance containing project details.
38
+ * @throws {Error} If `clasp.project.projectId` is not set.
39
+ */
13
40
  export function assertGcpProjectConfigured(clasp) {
14
41
  if (clasp.project.projectId) {
15
42
  return;
@@ -17,6 +44,13 @@ export function assertGcpProjectConfigured(clasp) {
17
44
  const msg = intl.formatMessage({ id: "aD3+By", defaultMessage: [{ type: 0, value: "GCP project ID is not set, unable to continue." }] });
18
45
  throw new Error(msg);
19
46
  }
47
+ /**
48
+ * Prompts the user for a Google Cloud Platform (GCP) project ID if one is not already configured
49
+ * and the environment is interactive. It opens the script's GCP settings page in the browser
50
+ * to help the user find their project ID.
51
+ * @param {Clasp} clasp - The Clasp instance.
52
+ * @returns {Promise<string | undefined>} The GCP project ID if available or entered by the user, otherwise undefined.
53
+ */
20
54
  export async function maybePromptForProjectId(clasp) {
21
55
  let projectId = clasp.project.getProjectId();
22
56
  if (!projectId && isInteractive()) {
@@ -41,6 +75,14 @@ export async function maybePromptForProjectId(clasp) {
41
75
  return projectId;
42
76
  }
43
77
  const spinner = ora();
78
+ /**
79
+ * Executes an asynchronous function while displaying a spinner in the console.
80
+ * The spinner is only shown if the environment is interactive.
81
+ * @template T
82
+ * @param {string} message - The message to display next to the spinner.
83
+ * @param {() => Promise<T>} fn - The asynchronous function to execute.
84
+ * @returns {Promise<T>} A promise that resolves with the result of the executed function.
85
+ */
44
86
  export async function withSpinner(message, fn) {
45
87
  // If not interactive terminal, skip spinner
46
88
  if (!isInteractive()) {
@@ -56,17 +98,43 @@ export async function withSpinner(message, fn) {
56
98
  }
57
99
  }
58
100
  }
101
+ /**
102
+ * Truncates a string to a specified length with an ellipsis if it exceeds the length.
103
+ * It attempts to truncate on a space and pads the end to maintain the specified length.
104
+ * @param {string} value - The string to ellipsize.
105
+ * @param {number} length - The maximum length of the string.
106
+ * @returns {string} The ellipsized string.
107
+ */
59
108
  export function ellipsize(value, length) {
60
109
  return cliTruncate(value, length, { preferTruncationOnSpace: true }).padEnd(length);
61
110
  }
111
+ /**
112
+ * Environment flags for clasp, primarily used for testing purposes
113
+ * to simulate or disable interactive states or browser presence.
114
+ * @property {boolean} isInteractive - Whether the current environment is an interactive TTY.
115
+ * @property {boolean} isBrowserPresent - Whether a browser is considered available to open URLs.
116
+ */
62
117
  // Exporting and wrapping to allow it to be toggled in tests
63
118
  export const claspEnv = {
64
119
  isInteractive: process.stdout.isTTY,
65
120
  isBrowserPresent: process.stdout.isTTY,
66
121
  };
122
+ /**
123
+ * Checks if the current environment is interactive (i.e., a TTY).
124
+ * Relies on `claspEnv.isInteractive`.
125
+ * @returns {boolean} True if interactive, false otherwise.
126
+ */
67
127
  export function isInteractive() {
68
128
  return claspEnv.isInteractive;
69
129
  }
130
+ /**
131
+ * Opens a URL in the default web browser if available.
132
+ * If a browser is not considered present (e.g., in some CI environments, or for testing),
133
+ * it logs a message asking the user to open the URL manually.
134
+ * @param {string} url - The URL to open.
135
+ * @returns {Promise<void | import('open').ChildProcess>} A promise that resolves when the URL is opened,
136
+ * or void if a browser is not present. The child process from `open` is returned if a browser is used.
137
+ */
70
138
  export async function openUrl(url) {
71
139
  if (!claspEnv.isBrowserPresent) {
72
140
  const msg = intl.formatMessage({ id: "kvR0OI", defaultMessage: [{ type: 0, value: "Open " }, { type: 1, value: "url" }, { type: 0, value: " in your browser to continue." }] }, {
@@ -1,3 +1,18 @@
1
+ // Copyright 2020 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 project-wide constants, such as the project name and
15
+ // manifest file names, to ensure consistency across the codebase.
1
16
  // Names / Paths
2
17
  export const PROJECT_NAME = 'clasp';
3
18
  export const PROJECT_MANIFEST_BASENAME = 'appsscript';
@@ -1,6 +1,16 @@
1
- /**
2
- * Google API Types
3
- */
1
+ // Copyright 2018 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.
4
14
  /**
5
15
  * This is a list of all public Advanced Services.
6
16
  *