@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
@@ -8,11 +8,11 @@ describe('registerTools', () => {
8
8
  registerTool: mock.fn(),
9
9
  };
10
10
 
11
- const { registerTools } = await esmock('../../tools.js', {});
11
+ const { registerTools } = await esmock('../../tools/tools.js', {});
12
12
 
13
13
  registerTools(server);
14
14
 
15
- assert.strictEqual(server.registerTool.mock.callCount(), 9);
15
+ assert.strictEqual(server.registerTool.mock.callCount(), 8);
16
16
  const toolNames = server.registerTool.mock.calls.map(
17
17
  (call) => call.arguments[0]
18
18
  );
@@ -22,7 +22,6 @@ describe('registerTools', () => {
22
22
  'create_project',
23
23
  'deploy_container_image',
24
24
  'deploy_file_contents',
25
- 'deploy_local_files',
26
25
  'deploy_local_folder',
27
26
  'get_service',
28
27
  'get_service_log',
@@ -38,12 +37,16 @@ describe('registerTools', () => {
38
37
  registerTool: mock.fn(),
39
38
  };
40
39
 
41
- const { registerTools } = await esmock('../../tools.js', {
42
- '../../lib/gcp-projects.js': {
43
- listProjects: () =>
44
- Promise.resolve([{ id: 'project1' }, { id: 'project2' }]),
45
- },
46
- });
40
+ const { registerTools } = await esmock(
41
+ '../../tools/tools.js',
42
+ {},
43
+ {
44
+ '../../lib/cloud-api/projects.js': {
45
+ listProjects: () =>
46
+ Promise.resolve([{ id: 'project1' }, { id: 'project2' }]),
47
+ },
48
+ }
49
+ );
47
50
 
48
51
  registerTools(server, { gcpCredentialsAvailable: true });
49
52
 
@@ -69,15 +72,19 @@ describe('registerTools', () => {
69
72
  registerTool: mock.fn(),
70
73
  };
71
74
 
72
- const { registerTools } = await esmock('../../tools.js', {
73
- '../../lib/gcp-projects.js': {
74
- createProjectAndAttachBilling: (projectId) =>
75
- Promise.resolve({
76
- projectId: projectId,
77
- billingMessage: 'billing message',
78
- }),
79
- },
80
- });
75
+ const { registerTools } = await esmock(
76
+ '../../tools/tools.js',
77
+ {},
78
+ {
79
+ '../../lib/cloud-api/projects.js': {
80
+ createProjectAndAttachBilling: (projectId) =>
81
+ Promise.resolve({
82
+ projectId: projectId,
83
+ billingMessage: `Project ${projectId} created successfully. Billing attached.`,
84
+ }),
85
+ },
86
+ }
87
+ );
81
88
 
82
89
  registerTools(server, { gcpCredentialsAvailable: true });
83
90
 
@@ -90,7 +97,7 @@ describe('registerTools', () => {
90
97
  content: [
91
98
  {
92
99
  type: 'text',
93
- text: 'Successfully created GCP project with ID "my-project". You can now use this project ID for deployments.',
100
+ text: 'Project my-project created successfully. Billing attached.',
94
101
  },
95
102
  ],
96
103
  });
@@ -101,15 +108,20 @@ describe('registerTools', () => {
101
108
  registerTool: mock.fn(),
102
109
  };
103
110
 
104
- const { registerTools } = await esmock('../../tools.js', {
105
- '../../lib/gcp-projects.js': {
106
- createProjectAndAttachBilling: () =>
107
- Promise.resolve({
108
- projectId: 'generated-project',
109
- billingMessage: 'billing message',
110
- }),
111
- },
112
- });
111
+ const { registerTools } = await esmock(
112
+ '../../tools/tools.js',
113
+ {},
114
+ {
115
+ '../../lib/cloud-api/projects.js': {
116
+ createProjectAndAttachBilling: () =>
117
+ Promise.resolve({
118
+ projectId: 'generated-project',
119
+ billingMessage:
120
+ 'Project generated-project created successfully. Billing attached.',
121
+ }),
122
+ },
123
+ }
124
+ );
113
125
 
114
126
  registerTools(server, { gcpCredentialsAvailable: true });
115
127
 
@@ -122,7 +134,7 @@ describe('registerTools', () => {
122
134
  content: [
123
135
  {
124
136
  type: 'text',
125
- text: 'Successfully created GCP project with ID "generated-project". You can now use this project ID for deployments.',
137
+ text: 'Project generated-project created successfully. Billing attached.',
126
138
  },
127
139
  ],
128
140
  });
@@ -135,15 +147,25 @@ describe('registerTools', () => {
135
147
  registerTool: mock.fn(),
136
148
  };
137
149
 
138
- const { registerTools } = await esmock('../../tools.js', {
139
- '../../lib/cloud-run-services.js': {
140
- listServices: () =>
141
- Promise.resolve([
142
- { name: 'service1', uri: 'uri1' },
143
- { name: 'service2', uri: 'uri2' },
144
- ]),
145
- },
146
- });
150
+ const { registerTools } = await esmock(
151
+ '../../tools/tools.js',
152
+ {},
153
+ {
154
+ '../../lib/cloud-api/run.js': {
155
+ listServices: () =>
156
+ Promise.resolve({
157
+ 'my-region1': [
158
+ { name: 'service1', uri: 'uri1' },
159
+ { name: 'service2', uri: 'uri2' },
160
+ ],
161
+ 'my-region2': [
162
+ { name: 'service3', uri: 'uri3' },
163
+ { name: 'service4', uri: 'uri4' },
164
+ ],
165
+ }),
166
+ },
167
+ }
168
+ );
147
169
 
148
170
  registerTools(server, { gcpCredentialsAvailable: true });
149
171
 
@@ -152,14 +174,17 @@ describe('registerTools', () => {
152
174
  ).arguments[2];
153
175
  const result = await handler({
154
176
  project: 'my-project',
155
- region: 'my-region',
156
177
  });
157
178
 
158
179
  assert.deepStrictEqual(result, {
159
180
  content: [
160
181
  {
161
182
  type: 'text',
162
- text: 'Services in project my-project (location my-region):\n- service1 (URL: uri1)\n- service2 (URL: uri2)',
183
+ text: 'Services in project my-project (location my-region1):\n- service1 (URL: uri1)\n- service2 (URL: uri2)',
184
+ },
185
+ {
186
+ type: 'text',
187
+ text: 'Services in project my-project (location my-region2):\n- service3 (URL: uri3)\n- service4 (URL: uri4)',
163
188
  },
164
189
  ],
165
190
  });
@@ -172,16 +197,20 @@ describe('registerTools', () => {
172
197
  registerTool: mock.fn(),
173
198
  };
174
199
 
175
- const { registerTools } = await esmock('../../tools.js', {
176
- '../../lib/cloud-run-services.js': {
177
- getService: () =>
178
- Promise.resolve({
179
- name: 'my-service',
180
- uri: 'my-uri',
181
- lastModifier: 'me',
182
- }),
183
- },
184
- });
200
+ const { registerTools } = await esmock(
201
+ '../../tools/tools.js',
202
+ {},
203
+ {
204
+ '../../lib/cloud-api/run.js': {
205
+ getService: () =>
206
+ Promise.resolve({
207
+ name: 'my-service',
208
+ uri: 'my-uri',
209
+ lastModifier: 'me',
210
+ }),
211
+ },
212
+ }
213
+ );
185
214
 
186
215
  registerTools(server, { gcpCredentialsAvailable: true });
187
216
 
@@ -223,11 +252,15 @@ describe('registerTools', () => {
223
252
  return Promise.resolve({ logs: 'log3\nlog4', requestOptions: null });
224
253
  };
225
254
 
226
- const { registerTools } = await esmock('../../tools.js', {
227
- '../../lib/cloud-run-services.js': {
228
- getServiceLogs: getServiceLogs,
229
- },
230
- });
255
+ const { registerTools } = await esmock(
256
+ '../../tools/tools.js',
257
+ {},
258
+ {
259
+ '../../lib/cloud-api/run.js': {
260
+ getServiceLogs: getServiceLogs,
261
+ },
262
+ }
263
+ );
231
264
 
232
265
  registerTools(server, { gcpCredentialsAvailable: true });
233
266
 
@@ -251,64 +284,36 @@ describe('registerTools', () => {
251
284
  });
252
285
  });
253
286
 
254
- describe('deploy_local_files', () => {
255
- it('should deploy local files', async () => {
256
- const server = {
257
- registerTool: mock.fn(),
258
- };
259
-
260
- const { registerTools } = await esmock('../../tools.js', {
261
- '../../lib/cloud-run-deploy.js': {
262
- deploy: () => Promise.resolve({ uri: 'my-uri' }),
263
- },
264
- });
265
-
266
- registerTools(server, { gcpCredentialsAvailable: true });
267
-
268
- const handler = server.registerTool.mock.calls.find(
269
- (call) => call.arguments[0] === 'deploy_local_files'
270
- ).arguments[2];
271
- const result = await handler({
272
- project: 'my-project',
273
- region: 'my-region',
274
- service: 'my-service',
275
- files: ['file1', 'file2'],
276
- });
277
-
278
- assert.deepStrictEqual(result, {
279
- content: [
280
- {
281
- type: 'text',
282
- text: 'Cloud Run service my-service deployed in project my-project\nCloud Console: https://console.cloud.google.com/run/detail/my-region/my-service?project=my-project\nService URL: my-uri',
283
- },
284
- ],
285
- });
286
- });
287
- });
288
-
289
287
  describe('deploy_local_folder', () => {
290
288
  it('should deploy local folder', async () => {
291
289
  const server = {
292
290
  registerTool: mock.fn(),
293
291
  };
294
292
 
295
- const { registerTools } = await esmock('../../tools.js', {
296
- '../../lib/cloud-run-deploy.js': {
297
- deploy: () => Promise.resolve({ uri: 'my-uri' }),
298
- },
299
- });
293
+ const { registerTools } = await esmock(
294
+ '../../tools/tools.js',
295
+ {},
296
+ {
297
+ '../../lib/deployment/deployer.js': {
298
+ deploy: () => Promise.resolve({ uri: 'my-uri' }),
299
+ },
300
+ }
301
+ );
300
302
 
301
303
  registerTools(server, { gcpCredentialsAvailable: true });
302
304
 
303
305
  const handler = server.registerTool.mock.calls.find(
304
306
  (call) => call.arguments[0] === 'deploy_local_folder'
305
307
  ).arguments[2];
306
- const result = await handler({
307
- project: 'my-project',
308
- region: 'my-region',
309
- service: 'my-service',
310
- folderPath: '/my/folder',
311
- });
308
+ const result = await handler(
309
+ {
310
+ project: 'my-project',
311
+ region: 'my-region',
312
+ service: 'my-service',
313
+ folderPath: '/my/folder',
314
+ },
315
+ { sendNotification: mock.fn() }
316
+ );
312
317
 
313
318
  assert.deepStrictEqual(result, {
314
319
  content: [
@@ -327,23 +332,30 @@ describe('registerTools', () => {
327
332
  registerTool: mock.fn(),
328
333
  };
329
334
 
330
- const { registerTools } = await esmock('../../tools.js', {
331
- '../../lib/cloud-run-deploy.js': {
332
- deploy: () => Promise.resolve({ uri: 'my-uri' }),
333
- },
334
- });
335
+ const { registerTools } = await esmock(
336
+ '../../tools/tools.js',
337
+ {},
338
+ {
339
+ '../../lib/deployment/deployer.js': {
340
+ deploy: () => Promise.resolve({ uri: 'my-uri' }),
341
+ },
342
+ }
343
+ );
335
344
 
336
345
  registerTools(server, { gcpCredentialsAvailable: true });
337
346
 
338
347
  const handler = server.registerTool.mock.calls.find(
339
348
  (call) => call.arguments[0] === 'deploy_file_contents'
340
349
  ).arguments[2];
341
- const result = await handler({
342
- project: 'my-project',
343
- region: 'my-region',
344
- service: 'my-service',
345
- files: [{ filename: 'file1', content: 'content1' }],
346
- });
350
+ const result = await handler(
351
+ {
352
+ project: 'my-project',
353
+ region: 'my-region',
354
+ service: 'my-service',
355
+ files: [{ filename: 'file1', content: 'content1' }],
356
+ },
357
+ { sendNotification: mock.fn() }
358
+ );
347
359
 
348
360
  assert.deepStrictEqual(result, {
349
361
  content: [
@@ -362,23 +374,30 @@ describe('registerTools', () => {
362
374
  registerTool: mock.fn(),
363
375
  };
364
376
 
365
- const { registerTools } = await esmock('../../tools.js', {
366
- '../../lib/cloud-run-deploy.js': {
367
- deployImage: () => Promise.resolve({ uri: 'my-uri' }),
368
- },
369
- });
377
+ const { registerTools } = await esmock(
378
+ '../../tools/tools.js',
379
+ {},
380
+ {
381
+ '../../lib/deployment/deployer.js': {
382
+ deployImage: () => Promise.resolve({ uri: 'my-uri' }),
383
+ },
384
+ }
385
+ );
370
386
 
371
387
  registerTools(server, { gcpCredentialsAvailable: true });
372
388
 
373
389
  const handler = server.registerTool.mock.calls.find(
374
390
  (call) => call.arguments[0] === 'deploy_container_image'
375
391
  ).arguments[2];
376
- const result = await handler({
377
- project: 'my-project',
378
- region: 'my-region',
379
- service: 'my-service',
380
- imageUrl: 'gcr.io/my-project/my-image',
381
- });
392
+ const result = await handler(
393
+ {
394
+ project: 'my-project',
395
+ region: 'my-region',
396
+ service: 'my-service',
397
+ imageUrl: 'gcr.io/my-project/my-image',
398
+ },
399
+ { sendNotification: mock.fn() }
400
+ );
382
401
 
383
402
  assert.deepStrictEqual(result, {
384
403
  content: [
@@ -397,7 +416,7 @@ describe('registerTools', () => {
397
416
  registerTool: mock.fn(),
398
417
  };
399
418
 
400
- const { registerTools } = await esmock('../../tools.js', {});
419
+ const { registerTools } = await esmock('../../tools/tools.js', {});
401
420
 
402
421
  registerTools(server, { gcpCredentialsAvailable: false });
403
422
 
@@ -14,96 +14,76 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import readline from 'readline/promises';
18
- import { stdin as input, stdout as output } from 'process';
19
- import { getServiceLogs } from '../../lib/cloud-run-services.js';
17
+ import { test } from 'node:test';
18
+ import { getServiceLogs, listServices } from '../../lib/cloud-api/run.js';
19
+ import assert from 'node:assert';
20
20
 
21
21
  /**
22
- * Prompts the user for required service information
23
- * @returns {Promise<{projectId: string, region: string, serviceId: string}>} The service details
22
+ * Get the GCP project ID from GOOGLE_CLOUD_PROJECT or command line argument.
23
+ * @returns {string} The GCP project ID
24
24
  */
25
- async function getServiceDetails() {
26
- const rl = readline.createInterface({ input, output });
27
-
28
- // Check command line arguments first
29
- const [, , projectId, region = 'europe-west1', serviceId] = process.argv;
30
-
31
- if (projectId && serviceId) {
32
- console.log(`Using command line arguments:
33
- Project ID: ${projectId}
34
- Region: ${region}
35
- Service ID: ${serviceId}`);
36
- rl.close();
37
- return { projectId, region, serviceId };
25
+ function getProjectId() {
26
+ const projectId = process.env.GOOGLE_CLOUD_PROJECT || process.argv[2];
27
+ if (!projectId) {
28
+ console.error(
29
+ 'Usage: node <script> <projectId> or set GOOGLE_CLOUD_PROJECT'
30
+ );
31
+ process.exit(1);
38
32
  }
33
+ console.log(`Using Project ID: ${projectId}`);
34
+ return projectId;
35
+ }
39
36
 
40
- // If not provided via command line, prompt for the values
41
- const projectIdInput = await rl.question('Enter the Project ID: ');
42
- const regionInput = await rl.question(
43
- 'Enter the region (press Enter for europe-west1): '
44
- );
45
- const serviceIdInput = await rl.question('Enter the Service ID: ');
37
+ /**
38
+ * Gets service details from GOOGLE_CLOUD_PROJECT or command line arguments.
39
+ * @returns {{projectId: string, region: string, serviceId: string}} The service details
40
+ */
41
+ async function getServiceDetails() {
42
+ const projectId = getProjectId();
43
+ let region, serviceId;
46
44
 
47
- rl.close();
45
+ try {
46
+ const allServices = await listServices(projectId);
48
47
 
49
- return {
50
- projectId: projectIdInput.trim(),
51
- region: regionInput.trim() || 'europe-west1',
52
- serviceId: serviceIdInput.trim(),
53
- };
54
- }
48
+ if (!allServices || Object.keys(allServices).length === 0) {
49
+ assert.fail('No services found for the given project.');
50
+ }
55
51
 
56
- async function main() {
57
- try {
58
- const { projectId, region, serviceId } = await getServiceDetails();
52
+ const regions = Object.keys(allServices);
53
+ region = regions[0];
59
54
 
60
- if (!projectId || !serviceId) {
61
- console.error('Both Project ID and Service ID are required.');
62
- process.exit(1);
55
+ if (!allServices[region] || allServices[region].length === 0) {
56
+ assert.fail(`No services found in region: ${region}`);
63
57
  }
64
58
 
65
- console.log(
66
- `\nFetching logs for service "${serviceId}" in project "${projectId}" (region: ${region})...`
67
- );
59
+ const serviceName = allServices[region][0].name;
60
+ serviceId = serviceName.split('/').pop();
61
+ console.log(`Using region - ${region} and service ID - ${serviceId}`);
68
62
 
69
- let requestOptions;
70
- let pageCount = 0;
71
- const MAX_PAGES = 3; // Limit the number of pages to avoid too much output
72
-
73
- do {
74
- const result = await getServiceLogs(
75
- projectId,
76
- region,
77
- serviceId,
78
- requestOptions
79
- );
80
-
81
- if (result.logs) {
82
- console.log('\nLog entries:');
83
- console.log(result.logs);
84
- } else {
85
- console.log('No logs found for this service.');
86
- }
87
-
88
- requestOptions = result.requestOptions;
89
- pageCount++;
90
-
91
- if (requestOptions && pageCount < MAX_PAGES) {
92
- const rl = readline.createInterface({ input, output });
93
- const answer = await rl.question('\nFetch more logs? (y/N): ');
94
- rl.close();
95
-
96
- if (answer.toLowerCase() !== 'y') {
97
- break;
98
- }
99
- }
100
- } while (requestOptions && pageCount < MAX_PAGES);
101
-
102
- console.log('\nService logs test completed successfully.');
63
+ return { projectId, region, serviceId };
103
64
  } catch (error) {
104
- console.error('Error during service logs test:', error.message);
105
- process.exit(1);
65
+ // Better error handling for the API call itself
66
+ console.error('Error fetching services:', error.message);
67
+ throw error; // Re-throw the error to fail the test
106
68
  }
107
69
  }
108
70
 
109
- main();
71
+ test('should list services', async () => {
72
+ const projectId = getProjectId();
73
+ const services = await listServices(projectId);
74
+ console.log('Services found:', services ? Object.keys(services) : 'None');
75
+ console.log('All Services', services);
76
+ });
77
+
78
+ test('should fetch service logs', async () => {
79
+ const { projectId, region, serviceId } = await getServiceDetails();
80
+
81
+ const result = await getServiceLogs(projectId, region, serviceId);
82
+
83
+ if (result.logs) {
84
+ console.log('\nLog entries:');
85
+ console.log(result.logs);
86
+ } else {
87
+ console.log('No logs found for this service.');
88
+ }
89
+ });
@@ -0,0 +1,71 @@
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 { describe, test } from 'node:test';
18
+ import assert from 'node:assert';
19
+ import { deploy, deployImage } from '../../lib/deployment/deployer.js';
20
+
21
+ const projectId = process.env.GOOGLE_CLOUD_PROJECT || process.argv[2];
22
+ if (!projectId) {
23
+ console.error('Usage: node <script> <projectId> or set GOOGLE_CLOUD_PROJECT');
24
+ process.exit(1);
25
+ }
26
+
27
+ describe('Cloud Run Deployments', () => {
28
+ test('should fail to deploy without a service name', async () => {
29
+ const config = {
30
+ projectId: projectId,
31
+ region: 'europe-west1',
32
+ files: ['example-sources-to-deploy/main.go'],
33
+ };
34
+ await assert.rejects(deploy(config), {
35
+ message: 'Error: serviceName is required in the configuration object.',
36
+ });
37
+ });
38
+
39
+ test('should fail to deploy image without a service name', async () => {
40
+ const config = {
41
+ projectId: projectId,
42
+ region: 'europe-west1',
43
+ imageUrl: 'gcr.io/cloudrun/hello',
44
+ };
45
+ await assert.rejects(deployImage(config), {
46
+ message: 'Error: serviceName is required in the configuration object.',
47
+ });
48
+ });
49
+
50
+ test('should fail to deploy without a project id', async () => {
51
+ const config = {
52
+ serviceName: 'hello-from-image',
53
+ region: 'europe-west1',
54
+ files: ['example-sources-to-deploy/main.go'],
55
+ };
56
+ await assert.rejects(deploy(config), {
57
+ message: 'Error: projectId is required in the configuration object.',
58
+ });
59
+ });
60
+
61
+ test('should fail to deploy image without a project id', async () => {
62
+ const config = {
63
+ serviceName: 'hello-from-image',
64
+ region: 'europe-west1',
65
+ imageUrl: 'gcr.io/cloudrun/hello',
66
+ };
67
+ await assert.rejects(deployImage(config), {
68
+ message: 'Error: projectId is required in the configuration object.',
69
+ });
70
+ });
71
+ });
@@ -17,7 +17,7 @@ limitations under the License.
17
17
  import assert from 'node:assert/strict';
18
18
  import { describe, it, mock, beforeEach, afterEach } from 'node:test';
19
19
  import { GoogleAuth } from 'google-auth-library';
20
- import { ensureGCPCredentials } from '../../lib/gcp-auth-check.js';
20
+ import { ensureGCPCredentials } from '../../lib/cloud-api/auth.js';
21
21
 
22
22
  describe('ensureGCPCredentials', () => {
23
23
  let originalConsoleLog;