@google-cloud/cloud-run-mcp 1.10.0 → 1.12.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.
- package/.github/workflows/npm-publish.yml +10 -6
- package/README.md +26 -0
- package/SECURITY.md +7 -0
- package/cloudbuild.yaml +37 -0
- package/example-sources-to-deploy/java/pom.xml +41 -0
- package/example-sources-to-deploy/java/src/main/java/com/example/helloworld/HelloworldApplication.java +21 -0
- package/example-sources-to-deploy/java/src/main/resources/application.properties +1 -0
- package/example-sources-to-deploy/nodejs/index.js +15 -0
- package/example-sources-to-deploy/nodejs/package.json +12 -0
- package/example-sources-to-deploy/python/pip-project/main.py +11 -0
- package/example-sources-to-deploy/python/pip-project/requirements.txt +2 -0
- package/example-sources-to-deploy/python/pyproject-project/main.py +11 -0
- package/example-sources-to-deploy/python/pyproject-project/pyproject.toml +10 -0
- package/lib/clients.js +142 -12
- package/lib/cloud-api/build.js +192 -28
- package/lib/cloud-api/run.js +62 -0
- package/lib/cloud-api/secrets.js +196 -0
- package/lib/cloud-api/storage.js +164 -16
- package/lib/deployment/compose.js +517 -0
- package/lib/deployment/constants.js +8 -0
- package/lib/deployment/deployer.js +283 -28
- package/lib/deployment/helpers.js +58 -1
- package/lib/deployment/universal-maker.js +21 -148
- package/lib/middleware/oauth.js +26 -15
- package/lib/util/archive.js +3 -3
- package/lib/util/artifacts.js +189 -0
- package/lib/util/helpers.js +109 -0
- package/mcp-server.js +83 -20
- package/package.json +7 -4
- package/skills/cloud-run/SKILL.md +109 -0
- package/test/local/archive.test.js +88 -0
- package/test/local/clients.test.js +307 -2
- package/test/local/cloud-api/build.test.js +330 -25
- package/test/local/cloud-api/run.test.js +129 -0
- package/test/local/cloud-api/secrets.test.js +291 -0
- package/test/local/cloud-api/storage.test.js +405 -0
- package/test/local/compose.test.js +618 -0
- package/test/local/deploy.test.js +408 -0
- package/test/local/deployment-helpers.test.js +100 -1
- package/test/local/mcp-server-streamable-http.test.js +132 -12
- package/test/local/middleware/oauth.test.js +106 -5
- package/test/local/test-utils.js +2 -18
- package/test/local/tools.test.js +43 -1
- package/test/local/universal-maker.test.js +23 -114
- package/test/local/util.test.js +361 -0
- package/test/need-gcp/deployer.test.js +1 -1
- package/test/need-gcp/test-helpers.js +5 -1
- package/test/need-gcp/workflows/deployment-workflows.test.js +172 -6
- package/tools/register-tools.js +98 -78
- /package/example-sources-to-deploy/{Dockerfile → golang/Dockerfile} +0 -0
- /package/example-sources-to-deploy/{go.mod → golang/go.mod} +0 -0
- /package/example-sources-to-deploy/{main.go → golang/main.go} +0 -0
package/mcp-server.js
CHANGED
|
@@ -29,11 +29,11 @@ import { registerPrompts } from './prompts.js';
|
|
|
29
29
|
import { checkGCP } from './lib/cloud-api/metadata.js';
|
|
30
30
|
import { ensureGCPCredentials } from './lib/cloud-api/auth.js';
|
|
31
31
|
import { extractAccessToken } from './lib/util/helpers.js';
|
|
32
|
+
import { getDefaultAccessToken, validateAccessToken } from './lib/clients.js';
|
|
32
33
|
import { oauthMiddleware } from './lib/middleware/oauth.js';
|
|
33
34
|
import { config } from '@dotenvx/dotenvx';
|
|
34
35
|
import {
|
|
35
36
|
SCOPES,
|
|
36
|
-
GCLOUD_AUTH,
|
|
37
37
|
BEARER_METHODS_SUPPORTED,
|
|
38
38
|
RESPONSE_TYPES_SUPPORTED,
|
|
39
39
|
} from './constants.js';
|
|
@@ -41,6 +41,16 @@ import {
|
|
|
41
41
|
//Suppress the warning related to missing .env file in case of non-OAuth mode
|
|
42
42
|
config({ quiet: true, ignore: ['MISSING_ENV_FILE'] });
|
|
43
43
|
|
|
44
|
+
if (
|
|
45
|
+
process.env.OAUTH_ENABLED === 'true' &&
|
|
46
|
+
!process.env.GOOGLE_OAUTH_AUDIENCE
|
|
47
|
+
) {
|
|
48
|
+
console.error(
|
|
49
|
+
'GOOGLE_OAUTH_AUDIENCE environment variable is required when OAuth is enabled.'
|
|
50
|
+
);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
const gcpInfo = await checkGCP();
|
|
45
55
|
let gcpCredentialsAvailable = false;
|
|
46
56
|
|
|
@@ -54,7 +64,11 @@ function makeLoggingCompatibleWithStdio() {
|
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
function shouldStartStdio() {
|
|
57
|
-
if (
|
|
67
|
+
if (
|
|
68
|
+
process.env.GCP_STDIO === 'false' ||
|
|
69
|
+
process.env.OAUTH_ENABLED === 'true' ||
|
|
70
|
+
(gcpInfo && gcpInfo.project)
|
|
71
|
+
) {
|
|
58
72
|
return false;
|
|
59
73
|
}
|
|
60
74
|
return true;
|
|
@@ -69,12 +83,15 @@ const envProjectId = process.env.GOOGLE_CLOUD_PROJECT || undefined;
|
|
|
69
83
|
const envRegion = process.env.GOOGLE_CLOUD_REGION;
|
|
70
84
|
const defaultServiceName = process.env.DEFAULT_SERVICE_NAME;
|
|
71
85
|
const skipIamCheck = process.env.SKIP_IAM_CHECK !== 'false';
|
|
86
|
+
// Values for RUN_INGRESS_POLICY could be: "INGRESS_TRAFFIC_ALL", "INGRESS_TRAFFIC_INTERNAL_ONLY", "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
|
|
87
|
+
const ingress = process.env.RUN_INGRESS_POLICY || undefined;
|
|
72
88
|
const enableHostValidation = process.env.ENABLE_HOST_VALIDATION === 'true';
|
|
73
89
|
const allowedHosts = process.env.ALLOWED_HOSTS
|
|
74
90
|
? process.env.ALLOWED_HOSTS.split(',')
|
|
75
91
|
: undefined;
|
|
76
92
|
|
|
77
|
-
async function getServer(accessToken =
|
|
93
|
+
async function getServer(accessToken = getDefaultAccessToken()) {
|
|
94
|
+
validateAccessToken(accessToken);
|
|
78
95
|
// Create an MCP server with implementation details
|
|
79
96
|
const server = new McpServer(
|
|
80
97
|
{
|
|
@@ -107,6 +124,7 @@ async function getServer(accessToken = GCLOUD_AUTH) {
|
|
|
107
124
|
defaultRegion: effectiveRegion,
|
|
108
125
|
defaultServiceName,
|
|
109
126
|
skipIamCheck,
|
|
127
|
+
ingress,
|
|
110
128
|
gcpCredentialsAvailable,
|
|
111
129
|
accessToken,
|
|
112
130
|
});
|
|
@@ -120,6 +138,7 @@ async function getServer(accessToken = GCLOUD_AUTH) {
|
|
|
120
138
|
defaultRegion: effectiveRegion,
|
|
121
139
|
defaultServiceName,
|
|
122
140
|
skipIamCheck,
|
|
141
|
+
ingress,
|
|
123
142
|
gcpCredentialsAvailable,
|
|
124
143
|
accessToken,
|
|
125
144
|
});
|
|
@@ -185,9 +204,9 @@ if (shouldStartStdio()) {
|
|
|
185
204
|
|
|
186
205
|
app.post('/mcp', oauthMiddleware, async (req, res) => {
|
|
187
206
|
console.log('/mcp Received:', req.body);
|
|
188
|
-
const accessToken = extractAccessToken(req.headers.authorization);
|
|
189
|
-
const server = await getServer(accessToken);
|
|
190
207
|
try {
|
|
208
|
+
const accessToken = extractAccessToken(req.headers.authorization);
|
|
209
|
+
const server = await getServer(accessToken);
|
|
191
210
|
const transport = new StreamableHTTPServerTransport({
|
|
192
211
|
sessionIdGenerator: undefined,
|
|
193
212
|
});
|
|
@@ -245,28 +264,72 @@ if (shouldStartStdio()) {
|
|
|
245
264
|
const sseTransports = {};
|
|
246
265
|
|
|
247
266
|
// Legacy SSE endpoint for older clients
|
|
248
|
-
app.get('/sse', async (req, res) => {
|
|
267
|
+
app.get('/sse', oauthMiddleware, async (req, res) => {
|
|
249
268
|
console.log('/sse Received:', req.body);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
269
|
+
try {
|
|
270
|
+
const accessToken = extractAccessToken(req.headers.authorization);
|
|
271
|
+
const server = await getServer(accessToken);
|
|
272
|
+
// Create SSE transport for legacy clients
|
|
273
|
+
const transport = new SSEServerTransport('/messages', res);
|
|
274
|
+
sseTransports[transport.sessionId] = { transport, accessToken };
|
|
275
|
+
|
|
276
|
+
res.on('close', () => {
|
|
277
|
+
delete sseTransports[transport.sessionId];
|
|
278
|
+
});
|
|
259
279
|
|
|
260
|
-
|
|
280
|
+
await server.connect(transport);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('Error handling SSE request:', error);
|
|
283
|
+
if (!res.headersSent) {
|
|
284
|
+
res.status(500).json({
|
|
285
|
+
jsonrpc: '2.0',
|
|
286
|
+
error: {
|
|
287
|
+
code: -32603,
|
|
288
|
+
message: 'Internal server error',
|
|
289
|
+
},
|
|
290
|
+
id: null,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
261
294
|
});
|
|
262
295
|
|
|
263
296
|
// Legacy message endpoint for older clients
|
|
264
|
-
app.post('/messages', async (req, res) => {
|
|
297
|
+
app.post('/messages', oauthMiddleware, async (req, res) => {
|
|
265
298
|
console.log('/messages Received:', req.body);
|
|
266
299
|
const sessionId = req.query.sessionId;
|
|
267
|
-
const
|
|
268
|
-
if (
|
|
269
|
-
|
|
300
|
+
const session = sseTransports[sessionId];
|
|
301
|
+
if (session) {
|
|
302
|
+
const accessToken = extractAccessToken(req.headers.authorization);
|
|
303
|
+
// Session is intentionally bound to the /sse access token; clients must reconnect to /sse on token change.
|
|
304
|
+
if (
|
|
305
|
+
process.env.OAUTH_ENABLED === 'true' &&
|
|
306
|
+
session.accessToken !== accessToken
|
|
307
|
+
) {
|
|
308
|
+
return res.status(403).json({
|
|
309
|
+
jsonrpc: '2.0',
|
|
310
|
+
error: {
|
|
311
|
+
code: -32001,
|
|
312
|
+
message:
|
|
313
|
+
'Forbidden: session is bound to a different access token; reconnect to /sse',
|
|
314
|
+
},
|
|
315
|
+
id: null,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
await session.transport.handlePostMessage(req, res, req.body);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error('Error handling message request:', error);
|
|
322
|
+
if (!res.headersSent) {
|
|
323
|
+
res.status(500).json({
|
|
324
|
+
jsonrpc: '2.0',
|
|
325
|
+
error: {
|
|
326
|
+
code: -32603,
|
|
327
|
+
message: 'Internal server error',
|
|
328
|
+
},
|
|
329
|
+
id: null,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
270
333
|
} else {
|
|
271
334
|
res.status(400).send('No transport found for sessionId');
|
|
272
335
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@google-cloud/cloud-run-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Cloud Run MCP deployment tool",
|
|
6
6
|
"main": "mcp-server.js",
|
|
@@ -40,20 +40,23 @@
|
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://github.com/GoogleCloudPlatform/cloud-run-mcp#readme",
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@dotenvx/dotenvx": "^1.52.0",
|
|
43
44
|
"@google-cloud/artifact-registry": "^4.0.1",
|
|
44
45
|
"@google-cloud/billing": "^5.0.1",
|
|
45
46
|
"@google-cloud/cloudbuild": "^5.0.1",
|
|
46
47
|
"@google-cloud/logging": "^11.2.0",
|
|
47
48
|
"@google-cloud/resource-manager": "^6.0.1",
|
|
48
49
|
"@google-cloud/run": "^3.1.0",
|
|
50
|
+
"@google-cloud/secret-manager": "^6.1.1",
|
|
49
51
|
"@google-cloud/service-usage": "^4.1.0",
|
|
50
52
|
"@google-cloud/storage": "^7.16.0",
|
|
51
53
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
52
|
-
"archiver": "^
|
|
53
|
-
"@dotenvx/dotenvx": "^1.52.0",
|
|
54
|
+
"archiver": "^8.0.0",
|
|
54
55
|
"express": "^5.1.0",
|
|
55
56
|
"google-proto-files": "^5.0.0",
|
|
56
|
-
"
|
|
57
|
+
"googleapis": "^171.4.0",
|
|
58
|
+
"js-yaml": "^4.1.1",
|
|
59
|
+
"zod": "^4.6.7"
|
|
57
60
|
},
|
|
58
61
|
"devDependencies": {
|
|
59
62
|
"c8": "^10.1.3",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloud-run
|
|
3
|
+
description: 'Manage Cloud Run services and jobs.'
|
|
4
|
+
metadata:
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
openclaw:
|
|
7
|
+
category: 'compute'
|
|
8
|
+
requires:
|
|
9
|
+
bins:
|
|
10
|
+
- gcloud
|
|
11
|
+
cliHelp: 'gcloud run --help'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# run (v1)
|
|
15
|
+
|
|
16
|
+
> **PREREQUISITE:** Ensure you have the `gcloud` CLI installed and authenticated with `gcloud auth login`. Set your project with `gcloud config set project [PROJECT_ID]`.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
gcloud run <resource> <method> [flags]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Helper Commands
|
|
23
|
+
|
|
24
|
+
| Command | Description |
|
|
25
|
+
| -------- | -------------------------------------------- |
|
|
26
|
+
| `deploy` | Create or update a Cloud Run service or job. |
|
|
27
|
+
|
|
28
|
+
## API Resources
|
|
29
|
+
|
|
30
|
+
### services
|
|
31
|
+
|
|
32
|
+
- `list` — List available services in the specified region.
|
|
33
|
+
- `describe` — Obtain details about a given service, such as its URL and configuration.
|
|
34
|
+
- `update` — Update Cloud Run environment variables, concurrency settings, and other configuration.
|
|
35
|
+
- `delete` — Delete a service and its associated revisions.
|
|
36
|
+
- `update-traffic` — Adjust the traffic assignments for a Cloud Run service.
|
|
37
|
+
- `proxy` — Proxy a service to localhost authenticating as the active account.
|
|
38
|
+
- `logs read` — Read logs for a Cloud Run service.
|
|
39
|
+
- `replace` - Create or replace a service from a YAML service specification.
|
|
40
|
+
- `add-iam-policy-binding` — Add IAM policy binding to a Cloud Run service (e.g., to make it public).
|
|
41
|
+
- `remove-iam-policy-binding` - Remove IAM policy binding of a Cloud Run service.
|
|
42
|
+
- `get-iam-policy` - Get the IAM policy for a Cloud Run service.
|
|
43
|
+
- `set-iam-policy` - Set the IAM policy for a Cloud Run service.
|
|
44
|
+
|
|
45
|
+
### jobs
|
|
46
|
+
|
|
47
|
+
- `create` — Create a Cloud Run job.
|
|
48
|
+
- `execute` — Start an execution of a Cloud Run job.
|
|
49
|
+
- `list` — List available jobs.
|
|
50
|
+
- `describe` — Obtain details about a given job.
|
|
51
|
+
- `deploy` - Create or update a Cloud Run job
|
|
52
|
+
- `update` — Update a Cloud Run job configuration.
|
|
53
|
+
- `delete` — Delete a Cloud Run job.
|
|
54
|
+
- `replace` - Create or replace a Cloud Run job from a YAML job specification.
|
|
55
|
+
- `executions list` — List executions of a Cloud Run job.
|
|
56
|
+
- `logs read` — Read logs for a Cloud Run job.
|
|
57
|
+
- `add-iam-policy-binding` — Add IAM policy binding to a Cloud Run job.
|
|
58
|
+
- `remove-iam-policy-binding` - Remove IAM policy binding of a Cloud Run job.
|
|
59
|
+
- `get-iam-policy` - Get the IAM policy for a Cloud Run job.
|
|
60
|
+
- `set-iam-policy` - Set the IAM policy for a Cloud Run job.
|
|
61
|
+
|
|
62
|
+
### domain-mappings
|
|
63
|
+
|
|
64
|
+
- `list` — List domain mappings.
|
|
65
|
+
- `create` — Create a new domain mapping.
|
|
66
|
+
- `describe` — Obtain details about a domain mapping.
|
|
67
|
+
- `delete` — Delete a domain mapping.
|
|
68
|
+
|
|
69
|
+
### multi-region-services
|
|
70
|
+
|
|
71
|
+
- `list` — List multi-region services.
|
|
72
|
+
- `describe` — Obtain details about a multi-region service.
|
|
73
|
+
- `update` — Update settings for multi-region services.
|
|
74
|
+
- `delete` — Delete a multi-region service.
|
|
75
|
+
- `replace` - Create or Update multi-region service from YAML specification.
|
|
76
|
+
|
|
77
|
+
### revisions
|
|
78
|
+
|
|
79
|
+
- `list` — List available revisions for a service.
|
|
80
|
+
- `describe` — Obtain details about a specific revision.
|
|
81
|
+
- `delete` — Delete a specific revision.
|
|
82
|
+
|
|
83
|
+
### regions
|
|
84
|
+
|
|
85
|
+
- `list` — View available Cloud Run (fully managed) regions.
|
|
86
|
+
|
|
87
|
+
### compose
|
|
88
|
+
|
|
89
|
+
- `up` — Deploy to Cloud Run from a compose specification.
|
|
90
|
+
|
|
91
|
+
## Discovering Commands
|
|
92
|
+
|
|
93
|
+
Before calling any command, inspect it for help:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Browse resources and methods
|
|
97
|
+
gcloud run --help
|
|
98
|
+
|
|
99
|
+
# Inspect a specific resource methods
|
|
100
|
+
gcloud run services --help
|
|
101
|
+
|
|
102
|
+
# Inspect a specific resource's sub-group methods
|
|
103
|
+
gcloud run jobs executions --help
|
|
104
|
+
|
|
105
|
+
# Inspect a method's specific flags and arguments
|
|
106
|
+
gcloud run deploy --help
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use the output of `--help` to discover available flags like `--image`, `--env-vars`, `--memory`, etc.
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { test, describe, beforeEach, afterEach } from 'node:test';
|
|
18
|
+
import assert from 'node:assert/strict';
|
|
19
|
+
import fs from 'fs';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import os from 'os';
|
|
22
|
+
import { zipFiles } from '../../lib/util/archive.js';
|
|
23
|
+
|
|
24
|
+
describe('Archive Utility (zipFiles)', () => {
|
|
25
|
+
let tempDir;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'archive-test-'));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
if (tempDir && fs.existsSync(tempDir)) {
|
|
33
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('creates a valid zip archive in memory from buffer objects', async () => {
|
|
38
|
+
const files = [
|
|
39
|
+
{ filename: 'file1.txt', content: 'Hello World' },
|
|
40
|
+
{ filename: 'file2.json', content: JSON.stringify({ key: 'value' }) },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const zipBuffer = await zipFiles(files, false);
|
|
44
|
+
assert.ok(Buffer.isBuffer(zipBuffer));
|
|
45
|
+
assert.ok(zipBuffer.length > 0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('creates a valid tar.gz archive in memory from buffer objects', async () => {
|
|
49
|
+
const files = [{ filename: 'file1.txt', content: 'Hello World Tar' }];
|
|
50
|
+
|
|
51
|
+
const tarBuffer = await zipFiles(files, true);
|
|
52
|
+
assert.ok(Buffer.isBuffer(tarBuffer));
|
|
53
|
+
assert.ok(tarBuffer.length > 0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('creates archive from actual file paths on disk', async () => {
|
|
57
|
+
const testFilePath = path.join(tempDir, 'sample.txt');
|
|
58
|
+
fs.writeFileSync(testFilePath, 'Sample file content');
|
|
59
|
+
|
|
60
|
+
const zipBuffer = await zipFiles([testFilePath], false);
|
|
61
|
+
assert.ok(Buffer.isBuffer(zipBuffer));
|
|
62
|
+
assert.ok(zipBuffer.length > 0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('creates archive from directory path on disk', async () => {
|
|
66
|
+
const subDir = path.join(tempDir, 'subdir');
|
|
67
|
+
fs.mkdirSync(subDir);
|
|
68
|
+
fs.writeFileSync(path.join(subDir, 'nested.txt'), 'Nested content');
|
|
69
|
+
|
|
70
|
+
const zipBuffer = await zipFiles([subDir], false);
|
|
71
|
+
assert.ok(Buffer.isBuffer(zipBuffer));
|
|
72
|
+
assert.ok(zipBuffer.length > 0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('throws error if file path does not exist', async () => {
|
|
76
|
+
const nonExistentPath = path.join(tempDir, 'does-not-exist.txt');
|
|
77
|
+
await assert.rejects(
|
|
78
|
+
async () => {
|
|
79
|
+
await zipFiles([nonExistentPath], false);
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
message: new RegExp(
|
|
83
|
+
`File or directory not found: ${nonExistentPath.replace(/\\/g, '\\\\')}`
|
|
84
|
+
),
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
});
|