@google-cloud/cloud-run-mcp 1.3.0 → 1.6.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 (44) hide show
  1. package/.c8rc.json +9 -0
  2. package/.github/images/deploy_from_apps.gif +0 -0
  3. package/.github/images/deploy_from_ide.gif +0 -0
  4. package/.github/images/deploycli.gif +0 -0
  5. package/.github/workflows/lint-checker.yml +17 -0
  6. package/.github/workflows/local-tests.yml +1 -1
  7. package/.kokoro/kokoro_build.sh +22 -0
  8. package/.kokoro/presubmit.cfg +21 -0
  9. package/.kokoro/run_tests.sh +31 -0
  10. package/CONTRIBUTING.md +29 -8
  11. package/README.md +65 -19
  12. package/REVIEWING.md +20 -0
  13. package/gemini-extension/GEMINI.md +3 -3
  14. package/lib/cloud-api/build.js +200 -0
  15. package/lib/{gcp-api-helpers.js → cloud-api/helpers.js} +12 -8
  16. package/lib/{gcp-projects.js → cloud-api/projects.js} +58 -28
  17. package/lib/cloud-api/registry.js +98 -0
  18. package/lib/{cloud-run-services.js → cloud-api/run.js} +103 -21
  19. package/lib/cloud-api/storage.js +123 -0
  20. package/lib/deployment/deployer.js +474 -0
  21. package/lib/util/archive.js +95 -0
  22. package/lib/util/helpers.js +42 -0
  23. package/mcp-server.js +29 -11
  24. package/package.json +9 -6
  25. package/test/local/cloud-api/build.test.js +436 -0
  26. package/test/local/cloud-api/projects.test.js +18 -0
  27. package/test/local/mcp-server-streamable-http.test.js +57 -0
  28. package/test/local/mcp-server.test.js +0 -1
  29. package/test/local/notifications.test.js +48 -0
  30. package/test/local/tools.test.js +143 -124
  31. package/test/need-gcp/cloud-run-services.test.js +57 -77
  32. package/test/need-gcp/deployer.test.js +71 -0
  33. package/test/need-gcp/gcp-auth-check.test.js +1 -1
  34. package/test/need-gcp/gcp-projects.test.js +6 -60
  35. package/test/need-gcp/test-helpers.js +175 -0
  36. package/test/need-gcp/workflows/deployment-workflows.test.js +100 -0
  37. package/tools/register-tools.js +612 -0
  38. package/tools/tools.js +48 -0
  39. package/lib/cloud-run-deploy.js +0 -917
  40. package/test/need-gcp/cloud-run-deploy.test.js +0 -158
  41. package/tools.js +0 -969
  42. /package/lib/{gcp-auth-check.js → cloud-api/auth.js} +0 -0
  43. /package/lib/{gcp-billing-accounts.js → cloud-api/billing.js} +0 -0
  44. /package/lib/{gcp-metadata.js → cloud-api/metadata.js} +0 -0
@@ -17,7 +17,7 @@ limitations under the License.
17
17
  import {
18
18
  listBillingAccounts,
19
19
  attachProjectToBillingAccount,
20
- } from './gcp-billing-accounts.js';
20
+ } from './billing.js';
21
21
 
22
22
  /**
23
23
  * Lists all accessible Google Cloud Platform projects.
@@ -39,40 +39,50 @@ export async function listProjects() {
39
39
  }
40
40
  }
41
41
 
42
+ /**
43
+ * Creates a compliant GCP project ID in the format 'mcp-cvc-cvc', where 'c' is a consonant and 'v' is a vowel.
44
+ * @function generateProjectId
45
+ * @returns {string} A randomly generated, compliant GCP project ID in the format 'mcp-cvc-cvc'.
46
+ */
47
+ export function generateProjectId() {
48
+ const consonants = 'bcdfghjklmnpqrstvwxyz';
49
+ const vowels = 'aeiou';
50
+
51
+ const getRandomChar = (source) =>
52
+ source.charAt(Math.floor(Math.random() * source.length));
53
+
54
+ const generateCVC = () => {
55
+ const c1 = getRandomChar(consonants);
56
+ const v = getRandomChar(vowels);
57
+ const c2 = getRandomChar(consonants);
58
+ return `${c1}${v}${c2}`;
59
+ };
60
+
61
+ const cvc1 = generateCVC();
62
+ const cvc2 = generateCVC();
63
+ return `mcp-${cvc1}-${cvc2}`;
64
+ }
65
+
42
66
  /**
43
67
  * Creates a new Google Cloud Platform project.
44
68
  * @async
45
69
  * @function createProject
46
70
  * @param {string} [projectId] - Optional. The desired ID for the new project. If not provided, a compliant ID will be generated automatically (e.g., app-cvc-cvc).
71
+ * @param {string} [parent] - Optional. The resource name of the parent under which the project is to be created. e.g., "organizations/123" or "folders/456".
47
72
  * @returns {Promise<{projectId: string}|null>} A promise that resolves to an object containing the new project's ID.
48
73
  */
49
- export async function createProject(projectId) {
74
+ export async function createProject(projectId, parent) {
50
75
  const { ProjectsClient } = await import('@google-cloud/resource-manager');
51
76
  const client = new ProjectsClient();
52
77
  let projectIdToUse = projectId;
53
78
 
54
79
  if (!projectIdToUse) {
55
- const consonants = 'bcdfghjklmnpqrstvwxyz';
56
- const vowels = 'aeiou';
57
-
58
- const getRandomChar = (source) =>
59
- source.charAt(Math.floor(Math.random() * source.length));
60
-
61
- const generateCVC = () => {
62
- const c1 = getRandomChar(consonants);
63
- const v = getRandomChar(vowels);
64
- const c2 = getRandomChar(consonants);
65
- return `${c1}${v}${c2}`;
66
- };
67
-
68
- const cvc1 = generateCVC();
69
- const cvc2 = generateCVC();
70
- projectIdToUse = `mcp-${cvc1}-${cvc2}`;
80
+ projectIdToUse = generateProjectId();
71
81
  console.log(`Project ID not provided, generated ID: ${projectIdToUse}`);
72
82
  }
73
83
 
74
84
  try {
75
- const projectPayload = { projectId: projectIdToUse };
85
+ const projectPayload = { projectId: projectIdToUse, parent };
76
86
 
77
87
  console.log(`Attempting to create project with ID: ${projectIdToUse}`);
78
88
 
@@ -100,12 +110,13 @@ export async function createProject(projectId) {
100
110
  * @async
101
111
  * @function createProjectAndAttachBilling
102
112
  * @param {string} [projectIdParam] - Optional. The desired ID for the new project.
113
+ * @param {string} [parent] - Optional. The resource name of the parent under which the project is to be created. e.g., "organizations/123" or "folders/456".
103
114
  * @returns {Promise<{projectId: string, billingMessage: string}>} A promise that resolves to an object containing the project ID and a billing status message.
104
115
  */
105
- export async function createProjectAndAttachBilling(projectIdParam) {
116
+ export async function createProjectAndAttachBilling(projectIdParam, parent) {
106
117
  let newProject;
107
118
  try {
108
- newProject = await createProject(projectIdParam);
119
+ newProject = await createProject(projectIdParam, parent);
109
120
  } catch (error) {
110
121
  throw new Error(`Failed to create project: ${error.message}`);
111
122
  }
@@ -115,7 +126,7 @@ export async function createProjectAndAttachBilling(projectIdParam) {
115
126
  }
116
127
 
117
128
  const { projectId } = newProject;
118
- let billingMessage = `Project ${projectId} created.`;
129
+ let billingMessage = `Project ${projectId} created successfully.`;
119
130
 
120
131
  try {
121
132
  const billingAccounts = await listBillingAccounts();
@@ -130,27 +141,46 @@ export async function createProjectAndAttachBilling(projectIdParam) {
130
141
  firstBillingAccount.name
131
142
  );
132
143
  if (billingInfo && billingInfo.billingEnabled) {
133
- billingMessage += ` Successfully attached to billing account ${firstBillingAccount.displayName}.`;
144
+ billingMessage += ` It has been attached to billing account ${firstBillingAccount.displayName}.`;
134
145
  } else {
135
- billingMessage += ` Could not attach to billing account ${firstBillingAccount.displayName} or billing not enabled. Please check manually. Console: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
146
+ billingMessage += ` However, it could not be attached to billing account ${firstBillingAccount.displayName} or billing not enabled. Please check manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
136
147
  }
137
148
  } else {
138
149
  const allBillingAccounts = billingAccounts
139
150
  .map((b) => `${b.displayName} (Open: ${b.open})`)
140
151
  .join(', ');
141
- billingMessage += ` No open billing accounts found. Available (may not be usable): ${allBillingAccounts || 'None'}. Please link billing manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
152
+ billingMessage += ` However, no open billing accounts were found. Available (may not be usable): ${allBillingAccounts || 'None'}. Please link billing manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
142
153
  }
143
154
  } else {
144
- billingMessage +=
145
- ' No billing accounts found. Please link billing manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}';
155
+ billingMessage += ` However, no billing accounts were found. Please link billing manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
146
156
  }
147
157
  } catch (billingError) {
148
158
  console.error(
149
159
  `Error during billing operations for project ${projectId}:`,
150
160
  billingError
151
161
  );
152
- billingMessage += ` Error during billing operations: ${billingError.message}. Please check manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
162
+ billingMessage += ` However, an error occurred during billing operations: ${billingError.message}. Please check manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
153
163
  }
154
164
 
155
165
  return { projectId, billingMessage };
156
166
  }
167
+
168
+ /**
169
+ * Deletes a Google Cloud Platform project.
170
+ * @async
171
+ * @function deleteProject
172
+ * @param {string} projectId - The ID of the project to delete.
173
+ * @returns {Promise<void>} A promise that resolves when the delete operation is initiated.
174
+ */
175
+ export async function deleteProject(projectId) {
176
+ const { ProjectsClient } = await import('@google-cloud/resource-manager');
177
+ const client = new ProjectsClient();
178
+ try {
179
+ console.log(`Attempting to delete project with ID: ${projectId}`);
180
+ await client.deleteProject({ name: `projects/${projectId}` });
181
+ console.log(`Project ${projectId} deletion initiated successfully.`);
182
+ } catch (error) {
183
+ console.error(`Error deleting GCP project ${projectId}:`, error.message);
184
+ throw error; // Re-throw to be caught by the caller
185
+ }
186
+ }
@@ -0,0 +1,98 @@
1
+ /*
2
+ Copyright 2025 Google LLC
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { callWithRetry } from './helpers.js';
18
+ import { logAndProgress } from '../util/helpers.js';
19
+
20
+ /**
21
+ * Ensures that an Artifact Registry repository exists.
22
+ * If the repository does not exist, it attempts to create it with the specified format.
23
+ *
24
+ * @async
25
+ * @param {object} context - The context object containing clients and other parameters.
26
+ * @param {string} projectId - The Google Cloud project ID.
27
+ * @param {string} location - The Google Cloud region for the repository.
28
+ * @param {string} repositoryId - The ID for the Artifact Registry repository.
29
+ * @param {string} [format='DOCKER'] - The format of the repository (e.g., 'DOCKER', 'NPM'). Defaults to 'DOCKER'.
30
+ * @param {function(object): void} [progressCallback] - Optional callback for progress updates.
31
+ * @returns {Promise<object>} A promise that resolves with the Artifact Registry repository object.
32
+ * @throws {Error} If there's an error checking or creating the repository.
33
+ */
34
+ export async function ensureArtifactRegistryRepoExists(
35
+ context,
36
+ projectId,
37
+ location,
38
+ repositoryId,
39
+ format = 'DOCKER',
40
+ progressCallback
41
+ ) {
42
+ const parent = `projects/${projectId}/locations/${location}`;
43
+ const repoPath = context.artifactRegistryClient.repositoryPath(
44
+ projectId,
45
+ location,
46
+ repositoryId
47
+ );
48
+
49
+ try {
50
+ const [repository] = await callWithRetry(
51
+ () => context.artifactRegistryClient.getRepository({ name: repoPath }),
52
+ `artifactRegistry.getRepository ${repositoryId}`
53
+ );
54
+ await logAndProgress(
55
+ `Repository ${repositoryId} already exists in ${location}.`,
56
+ progressCallback
57
+ );
58
+ return repository;
59
+ } catch (error) {
60
+ if (error.code === 5) {
61
+ await logAndProgress(
62
+ `Repository ${repositoryId} does not exist in ${location}. Creating...`,
63
+ progressCallback
64
+ );
65
+ const repositoryToCreate = {
66
+ format: format,
67
+ };
68
+
69
+ const [operation] = await callWithRetry(
70
+ () =>
71
+ context.artifactRegistryClient.createRepository({
72
+ parent: parent,
73
+ repository: repositoryToCreate,
74
+ repositoryId: repositoryId,
75
+ }),
76
+ `artifactRegistry.createRepository ${repositoryId}`
77
+ );
78
+ await logAndProgress(
79
+ `Creating Artifact Registry repository ${repositoryId}...`,
80
+ progressCallback
81
+ );
82
+ const [result] = await operation.promise();
83
+ await logAndProgress(
84
+ `Artifact Registry repository ${result.name} created successfully.`,
85
+ progressCallback
86
+ );
87
+ return result;
88
+ } else {
89
+ const errorMessage = `Error checking/creating repository ${repositoryId}: ${error.message}`;
90
+ console.error(
91
+ `Error checking/creating repository ${repositoryId}:`,
92
+ error
93
+ );
94
+ await logAndProgress(errorMessage, progressCallback, 'error');
95
+ throw error;
96
+ }
97
+ }
98
+ }
@@ -15,42 +15,74 @@ limitations under the License.
15
15
  */
16
16
 
17
17
  import protofiles from 'google-proto-files';
18
- import { callWithRetry, ensureApisEnabled } from './gcp-api-helpers.js';
18
+ import { callWithRetry, ensureApisEnabled } from './helpers.js';
19
+ import { logAndProgress } from '../util/helpers.js';
19
20
 
20
21
  let runClient;
22
+ let serviceUsageClient;
21
23
  let loggingClient;
22
24
 
25
+ async function listCloudRunLocations(projectId) {
26
+ const listLocationsRequest = {
27
+ name: `projects/${projectId}`,
28
+ };
29
+
30
+ const availableLocations = [];
31
+ try {
32
+ console.log('Listing Cloud Run supported locations:');
33
+ const iterable = runClient.listLocationsAsync(listLocationsRequest);
34
+ for await (const location of iterable) {
35
+ if (location.labels.initialized) {
36
+ console.log(`${location.locationId}: ${location.name}`);
37
+ availableLocations.push(location.locationId);
38
+ }
39
+ }
40
+ } catch (err) {
41
+ console.error('Error listing locations:', err);
42
+ throw err;
43
+ }
44
+ return availableLocations;
45
+ }
46
+
23
47
  /**
24
- * Lists all Cloud Run services in a given project and location.
48
+ * Lists all Cloud Run services in a given project.
25
49
  * @param {string} projectId - The Google Cloud project ID.
26
- * @param {string} location - The Google Cloud location (e.g., 'europe-west1').
27
- * @returns {Promise<Array<object>>} - A promise that resolves to an array of service objects.
50
+ * @returns {Promise<object>} - A promise that resolves to an object mapping region to list of service objects in that region.
28
51
  */
29
- export async function listServices(projectId, location) {
30
- // TODO make location optional, if not provided, list all services from all regions
52
+ export async function listServices(projectId) {
31
53
  if (!runClient) {
32
54
  const { v2 } = await import('@google-cloud/run');
33
55
  const { ServicesClient } = v2;
56
+ const { ServiceUsageClient } = await import('@google-cloud/service-usage');
34
57
  runClient = new ServicesClient({ projectId });
58
+ serviceUsageClient = new ServiceUsageClient({ projectId });
35
59
  }
60
+ const context = {
61
+ runClient: runClient,
62
+ serviceUsageClient: serviceUsageClient,
63
+ };
64
+ await ensureApisEnabled(context, projectId, ['run.googleapis.com']);
65
+ const locations = await listCloudRunLocations(projectId);
36
66
 
37
- await ensureApisEnabled(projectId, ['run.googleapis.com']);
67
+ const allServices = {};
68
+ for (const location of locations) {
69
+ const parent = runClient.locationPath(projectId, location);
38
70
 
39
- const parent = runClient.locationPath(projectId, location);
40
-
41
- try {
42
- console.log(
43
- `Listing Cloud Run services in project ${projectId}, location ${location}...`
44
- );
45
- const [services] = await callWithRetry(
46
- () => runClient.listServices({ parent }),
47
- 'listServices'
48
- );
49
- return services;
50
- } catch (error) {
51
- console.error(`Error listing Cloud Run services:`, error);
52
- throw error;
71
+ try {
72
+ console.log(
73
+ `Listing Cloud Run services in project ${projectId}, location ${location}...`
74
+ );
75
+ const [services] = await callWithRetry(
76
+ () => runClient.listServices({ parent }),
77
+ 'listServices'
78
+ );
79
+ allServices[location] = services;
80
+ } catch (error) {
81
+ console.error(`Error listing Cloud Run services:`, error);
82
+ throw error;
83
+ }
53
84
  }
85
+ return allServices;
54
86
  }
55
87
 
56
88
  /**
@@ -191,3 +223,53 @@ function formatLogEntry(entry) {
191
223
  }
192
224
  return `[${timestampStr}] [${severity}] ${responseData} ${data}`;
193
225
  }
226
+
227
+ /**
228
+ * Checks if a Cloud Run service already exists.
229
+ *
230
+ * @async
231
+ * @param {object} context - The context object containing clients and other parameters.
232
+ * @param {string} projectId - The Google Cloud project ID.
233
+ * @param {string} location - The Google Cloud region where the service is located.
234
+ * @param {string} serviceId - The ID of the Cloud Run service.
235
+ * @param {function(object): void} [progressCallback] - Optional callback for progress updates.
236
+ * @returns {Promise<boolean>} A promise that resolves to true if the service exists, false otherwise.
237
+ * @throws {Error} If there's an error checking the service (other than not found).
238
+ */
239
+ export async function checkCloudRunServiceExists(
240
+ context,
241
+ projectId,
242
+ location,
243
+ serviceId,
244
+ progressCallback
245
+ ) {
246
+ const parent = context.runClient.locationPath(projectId, location);
247
+ const servicePath = context.runClient.servicePath(
248
+ projectId,
249
+ location,
250
+ serviceId
251
+ );
252
+ try {
253
+ await callWithRetry(
254
+ () => context.runClient.getService({ name: servicePath }),
255
+ `getService ${serviceId}`
256
+ );
257
+ await logAndProgress(
258
+ `Cloud Run service ${serviceId} already exists.`,
259
+ progressCallback
260
+ );
261
+ return true;
262
+ } catch (error) {
263
+ if (error.code === 5) {
264
+ await logAndProgress(
265
+ `Cloud Run service ${serviceId} does not exist.`,
266
+ progressCallback
267
+ );
268
+ return false;
269
+ }
270
+ const errorMessage = `Error checking Cloud Run service ${serviceId}: ${error.message}`;
271
+ console.error(`Error checking Cloud Run service ${serviceId}:`, error);
272
+ await logAndProgress(errorMessage, progressCallback, 'error');
273
+ throw error;
274
+ }
275
+ }
@@ -0,0 +1,123 @@
1
+ /*
2
+ Copyright 2025 Google LLC
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { callWithRetry } from './helpers.js';
18
+ import { logAndProgress } from '../util/helpers.js';
19
+
20
+ /**
21
+ * Ensures that a Google Cloud Storage bucket exists.
22
+ * If the bucket does not exist, it attempts to create it in the specified location.
23
+ *
24
+ * @async
25
+ * @param {object} context - The context object containing clients and other parameters.
26
+ * @param {string} bucketName - The name of the storage bucket.
27
+ * @param {string} [location='us'] - The location to create the bucket in if it doesn't exist. Defaults to 'us'.
28
+ * @param {function(object): void} [progressCallback] - Optional callback for progress updates.
29
+ * @returns {Promise<import('@google-cloud/storage').Bucket>} A promise that resolves with the GCS Bucket object.
30
+ * @throws {Error} If there's an error checking or creating the bucket.
31
+ */
32
+ export async function ensureStorageBucketExists(
33
+ context,
34
+ bucketName,
35
+ location = 'us',
36
+ progressCallback
37
+ ) {
38
+ const bucket = context.storage.bucket(bucketName);
39
+ try {
40
+ const [exists] = await callWithRetry(
41
+ () => bucket.exists(),
42
+ `storage.bucket.exists ${bucketName}`
43
+ );
44
+ if (exists) {
45
+ await logAndProgress(
46
+ `Bucket ${bucketName} already exists.`,
47
+ progressCallback
48
+ );
49
+ return bucket;
50
+ } else {
51
+ await logAndProgress(
52
+ `Bucket ${bucketName} does not exist. Creating in location ${location}...`,
53
+ progressCallback
54
+ );
55
+ try {
56
+ const [createdBucket] = await callWithRetry(
57
+ () =>
58
+ context.storage.createBucket(bucketName, { location: location }),
59
+ `storage.createBucket ${bucketName}`
60
+ );
61
+ await logAndProgress(
62
+ `Storage bucket ${createdBucket.name} created successfully in ${location}.`,
63
+ progressCallback
64
+ );
65
+ return createdBucket;
66
+ } catch (createError) {
67
+ const errorMessage = `Failed to create storage bucket ${bucketName}. Error details: ${createError.message}`;
68
+ console.error(
69
+ `Failed to create storage bucket ${bucketName}. Error details:`,
70
+ createError
71
+ );
72
+ await logAndProgress(errorMessage, progressCallback, 'error');
73
+ throw createError;
74
+ }
75
+ }
76
+ } catch (error) {
77
+ const errorMessage = `Error checking/creating bucket ${bucketName}: ${error.message}`;
78
+ console.error(`Error checking/creating bucket ${bucketName}:`, error);
79
+ await logAndProgress(errorMessage, progressCallback, 'error');
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Uploads a buffer to a specified Google Cloud Storage bucket and blob name.
86
+ *
87
+ * @async
88
+ * @param {object} context - The context object containing clients and other parameters.
89
+ * @param {import('@google-cloud/storage').Bucket} bucket - The Google Cloud Storage bucket object.
90
+ * @param {Buffer} buffer - The buffer containing the data to upload.
91
+ * @param {string} destinationBlobName - The name for the blob in the bucket.
92
+ * @param {function(object): void} [progressCallback] - Optional callback for progress updates.
93
+ * @returns {Promise<import('@google-cloud/storage').File>} A promise that resolves with the GCS File object representing the uploaded blob.
94
+ * @throws {Error} If the upload fails.
95
+ */
96
+ export async function uploadToStorageBucket(
97
+ context,
98
+ bucket,
99
+ buffer,
100
+ destinationBlobName,
101
+ progressCallback
102
+ ) {
103
+ try {
104
+ await logAndProgress(
105
+ `Uploading buffer to gs://${bucket.name}/${destinationBlobName}...`,
106
+ progressCallback
107
+ );
108
+ await callWithRetry(
109
+ () => bucket.file(destinationBlobName).save(buffer),
110
+ `storage.bucket.file.save ${destinationBlobName}`
111
+ );
112
+ await logAndProgress(
113
+ `File ${destinationBlobName} uploaded successfully to gs://${bucket.name}/${destinationBlobName}.`,
114
+ progressCallback
115
+ );
116
+ return bucket.file(destinationBlobName);
117
+ } catch (error) {
118
+ const errorMessage = `Error uploading buffer: ${error.message}`;
119
+ console.error(`Error uploading buffer:`, error);
120
+ await logAndProgress(errorMessage, progressCallback, 'error');
121
+ throw error;
122
+ }
123
+ }