@vercel/sandbox 1.0.3 → 1.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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +18 -0
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +4 -4
- package/dist/api-client/api-client.d.ts +11 -3
- package/dist/api-client/api-client.js +6 -1
- package/dist/api-client/api-client.js.map +1 -0
- package/dist/api-client/api-error.js +1 -0
- package/dist/api-client/api-error.js.map +1 -0
- package/dist/api-client/base-client.d.ts +1 -0
- package/dist/api-client/base-client.js +2 -1
- package/dist/api-client/base-client.js.map +1 -0
- package/dist/api-client/file-writer.js +1 -0
- package/dist/api-client/file-writer.js.map +1 -0
- package/dist/api-client/index.js +1 -0
- package/dist/api-client/index.js.map +1 -0
- package/dist/api-client/validators.d.ts +23 -0
- package/dist/api-client/validators.js +2 -0
- package/dist/api-client/validators.js.map +1 -0
- package/dist/api-client/with-retry.js +11 -0
- package/dist/api-client/with-retry.js.map +1 -0
- package/dist/command.js +1 -0
- package/dist/command.js.map +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox.d.ts +14 -5
- package/dist/sandbox.js +27 -5
- package/dist/sandbox.js.map +1 -0
- package/dist/utils/array.js +1 -0
- package/dist/utils/array.js.map +1 -0
- package/dist/utils/consume-readable.js +1 -0
- package/dist/utils/consume-readable.js.map +1 -0
- package/dist/utils/decode-base64-url.js +1 -0
- package/dist/utils/decode-base64-url.js.map +1 -0
- package/dist/utils/get-credentials.js +1 -0
- package/dist/utils/get-credentials.js.map +1 -0
- package/dist/utils/jwt-expiry.js +1 -0
- package/dist/utils/jwt-expiry.js.map +1 -0
- package/dist/utils/normalizePath.js +1 -0
- package/dist/utils/normalizePath.js.map +1 -0
- package/dist/utils/resolveSignal.js +1 -0
- package/dist/utils/resolveSignal.js.map +1 -0
- package/dist/utils/types.js +1 -0
- package/dist/utils/types.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -1
- package/dist/version.js.map +1 -0
- package/package.json +2 -2
- package/src/api-client/api-client.ts +20 -4
- package/src/api-client/base-client.ts +7 -2
- package/src/api-client/validators.ts +1 -0
- package/src/api-client/with-retry.ts +11 -0
- package/src/command.test.ts +81 -77
- package/src/constants.ts +1 -0
- package/src/sandbox.test.ts +60 -58
- package/src/sandbox.ts +37 -10
- package/src/version.ts +1 -1
- package/tsconfig.json +1 -0
package/src/sandbox.test.ts
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
|
-
import { it, beforeEach, afterEach, expect } from "vitest";
|
|
1
|
+
import { it, beforeEach, afterEach, expect, describe } from "vitest";
|
|
2
2
|
import { consumeReadable } from "./utils/consume-readable";
|
|
3
3
|
import { Sandbox } from "./sandbox";
|
|
4
4
|
import { APIError } from "./api-client/api-error";
|
|
5
5
|
import ms from "ms";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
describe.skipIf(process.env.RUN_INTEGRATION_TESTS !== "1")("Sandbox", () => {
|
|
8
|
+
const PORTS = [3000, 4000];
|
|
9
|
+
let sandbox: Sandbox;
|
|
9
10
|
|
|
10
|
-
beforeEach(async () => {
|
|
11
|
-
|
|
12
|
-
});
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
sandbox = await Sandbox.create({ ports: PORTS });
|
|
13
|
+
});
|
|
13
14
|
|
|
14
|
-
afterEach(async () => {
|
|
15
|
-
|
|
16
|
-
});
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await sandbox.stop();
|
|
17
|
+
});
|
|
17
18
|
|
|
18
|
-
it("allows to write files and then read them", async () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
it("allows to write files and then read them", async () => {
|
|
20
|
+
await sandbox.writeFiles([
|
|
21
|
+
{ path: "hello1.txt", content: Buffer.from("Hello 1") },
|
|
22
|
+
{ path: "hello2.txt", content: Buffer.from("Hello 2") },
|
|
23
|
+
]);
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
25
|
+
const content1 = await sandbox.readFile({ path: "hello1.txt" });
|
|
26
|
+
const content2 = await sandbox.readFile({ path: "hello2.txt" });
|
|
27
|
+
expect((await consumeReadable(content1!)).toString()).toBe("Hello 1");
|
|
28
|
+
expect((await consumeReadable(content2!)).toString()).toBe("Hello 2");
|
|
29
|
+
});
|
|
29
30
|
|
|
30
|
-
it("verifies port forwarding works correctly", async () => {
|
|
31
|
-
|
|
31
|
+
it("verifies port forwarding works correctly", async () => {
|
|
32
|
+
const serverScript = `
|
|
32
33
|
const http = require('http');
|
|
33
34
|
const ports = process.argv.slice(2);
|
|
34
35
|
|
|
@@ -44,48 +45,49 @@ for (const port of ports) {
|
|
|
44
45
|
}
|
|
45
46
|
`;
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
await sandbox.writeFiles([
|
|
49
|
+
{ path: "server.js", content: Buffer.from(serverScript) },
|
|
50
|
+
]);
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const server = await sandbox.runCommand({
|
|
53
|
+
cmd: "node",
|
|
54
|
+
args: ["server.js", ...PORTS.map(String)],
|
|
55
|
+
detached: true,
|
|
56
|
+
stdout: process.stdout,
|
|
57
|
+
stderr: process.stderr,
|
|
58
|
+
});
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
for (const port of PORTS) {
|
|
63
|
+
const response = await fetch(sandbox.domain(port));
|
|
64
|
+
const text = await response.text();
|
|
65
|
+
expect(text).toBe(`hello port ${port}`);
|
|
66
|
+
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
});
|
|
68
|
+
await server.kill();
|
|
69
|
+
});
|
|
69
70
|
|
|
70
|
-
it("allows extending the sandbox timeout", async () => {
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
it("allows extending the sandbox timeout", async () => {
|
|
72
|
+
const originalTimeout = sandbox.timeout;
|
|
73
|
+
const extensionDuration = ms("5m");
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
75
|
+
await sandbox.extendTimeout(extensionDuration);
|
|
76
|
+
expect(sandbox.timeout).toEqual(originalTimeout + extensionDuration);
|
|
77
|
+
});
|
|
77
78
|
|
|
78
|
-
it("raises an error when the timeout cannot be updated", async () => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
it("raises an error when the timeout cannot be updated", async () => {
|
|
80
|
+
try {
|
|
81
|
+
await sandbox.extendTimeout(ms("5d"));
|
|
82
|
+
expect.fail("Expected extendTimeout to throw an error");
|
|
83
|
+
} catch (error) {
|
|
84
|
+
expect(error).toBeInstanceOf(APIError);
|
|
85
|
+
expect(error).toMatchObject({
|
|
86
|
+
response: { status: 400 },
|
|
87
|
+
json: {
|
|
88
|
+
error: { code: "sandbox_timeout_invalid" },
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
91
93
|
});
|
package/src/sandbox.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { APIClient } from "./api-client";
|
|
|
4
4
|
import { Command, type CommandFinished } from "./command";
|
|
5
5
|
import { type Credentials, getCredentials } from "./utils/get-credentials";
|
|
6
6
|
import { getPrivateParams, WithPrivate } from "./utils/types";
|
|
7
|
+
import { WithFetchOptions } from "./api-client/api-client";
|
|
8
|
+
import { RUNTIMES } from "./constants";
|
|
7
9
|
|
|
8
10
|
/** @inline */
|
|
9
11
|
export interface CreateSandboxParams {
|
|
@@ -50,10 +52,10 @@ export interface CreateSandboxParams {
|
|
|
50
52
|
resources?: { vcpus: number };
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
|
-
* The runtime of the sandbox, currently only `node22` and `python3.13` are supported.
|
|
55
|
+
* The runtime of the sandbox, currently only `node24`, `node22` and `python3.13` are supported.
|
|
54
56
|
* If not specified, the default runtime `node22` will be used.
|
|
55
57
|
*/
|
|
56
|
-
runtime?:
|
|
58
|
+
runtime?: RUNTIMES | (string & {});
|
|
57
59
|
|
|
58
60
|
/**
|
|
59
61
|
* An AbortSignal to cancel sandbox creation.
|
|
@@ -135,6 +137,10 @@ export class Sandbox {
|
|
|
135
137
|
return this.sandbox.id;
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
public get interactivePort(): number | undefined {
|
|
141
|
+
return this.sandbox.interactivePort ?? undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
138
144
|
/**
|
|
139
145
|
* The status of the sandbox.
|
|
140
146
|
*/
|
|
@@ -160,12 +166,15 @@ export class Sandbox {
|
|
|
160
166
|
* the next page of results.
|
|
161
167
|
*/
|
|
162
168
|
static async list(
|
|
163
|
-
params: Parameters<APIClient["listSandboxes"]>[0] &
|
|
169
|
+
params: Parameters<APIClient["listSandboxes"]>[0] &
|
|
170
|
+
Partial<Credentials> &
|
|
171
|
+
WithFetchOptions,
|
|
164
172
|
) {
|
|
165
173
|
const credentials = await getCredentials(params);
|
|
166
174
|
const client = new APIClient({
|
|
167
175
|
teamId: credentials.teamId,
|
|
168
176
|
token: credentials.token,
|
|
177
|
+
fetch: params.fetch,
|
|
169
178
|
});
|
|
170
179
|
return client.listSandboxes({
|
|
171
180
|
...params,
|
|
@@ -182,12 +191,14 @@ export class Sandbox {
|
|
|
182
191
|
static async create(
|
|
183
192
|
params?: WithPrivate<
|
|
184
193
|
CreateSandboxParams | (CreateSandboxParams & Credentials)
|
|
185
|
-
|
|
194
|
+
> &
|
|
195
|
+
WithFetchOptions,
|
|
186
196
|
): Promise<Sandbox> {
|
|
187
197
|
const credentials = await getCredentials(params);
|
|
188
198
|
const client = new APIClient({
|
|
189
199
|
teamId: credentials.teamId,
|
|
190
200
|
token: credentials.token,
|
|
201
|
+
fetch: params?.fetch,
|
|
191
202
|
});
|
|
192
203
|
|
|
193
204
|
const privateParams = getPrivateParams(params);
|
|
@@ -216,17 +227,21 @@ export class Sandbox {
|
|
|
216
227
|
* @returns A promise resolving to the {@link Sandbox}.
|
|
217
228
|
*/
|
|
218
229
|
static async get(
|
|
219
|
-
params: GetSandboxParams | (GetSandboxParams & Credentials)
|
|
230
|
+
params: WithPrivate<GetSandboxParams | (GetSandboxParams & Credentials)> &
|
|
231
|
+
WithFetchOptions,
|
|
220
232
|
): Promise<Sandbox> {
|
|
221
233
|
const credentials = await getCredentials(params);
|
|
222
234
|
const client = new APIClient({
|
|
223
235
|
teamId: credentials.teamId,
|
|
224
236
|
token: credentials.token,
|
|
237
|
+
fetch: params.fetch,
|
|
225
238
|
});
|
|
226
239
|
|
|
240
|
+
const privateParams = getPrivateParams(params);
|
|
227
241
|
const sandbox = await client.getSandbox({
|
|
228
242
|
sandboxId: params.sandboxId,
|
|
229
243
|
signal: params.signal,
|
|
244
|
+
...privateParams,
|
|
230
245
|
});
|
|
231
246
|
|
|
232
247
|
return new Sandbox({
|
|
@@ -351,12 +366,19 @@ export class Sandbox {
|
|
|
351
366
|
|
|
352
367
|
if (params.stdout || params.stderr) {
|
|
353
368
|
(async () => {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
369
|
+
try {
|
|
370
|
+
for await (const log of command.logs({ signal: params.signal })) {
|
|
371
|
+
if (log.stream === "stdout") {
|
|
372
|
+
params.stdout?.write(log.data);
|
|
373
|
+
} else if (log.stream === "stderr") {
|
|
374
|
+
params.stderr?.write(log.data);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} catch (err) {
|
|
378
|
+
if (params.signal?.aborted) {
|
|
379
|
+
return;
|
|
359
380
|
}
|
|
381
|
+
throw err;
|
|
360
382
|
}
|
|
361
383
|
})();
|
|
362
384
|
}
|
|
@@ -462,6 +484,11 @@ export class Sandbox {
|
|
|
462
484
|
* @param opts - Optional parameters.
|
|
463
485
|
* @param opts.signal - An AbortSignal to cancel the operation.
|
|
464
486
|
* @returns A promise that resolves when the timeout is extended
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* const sandbox = await Sandbox.create({ timeout: ms('10m') });
|
|
490
|
+
* // Extends timeout by 5 minutes, to a total of 15 minutes.
|
|
491
|
+
* await sandbox.extendTimeout(ms('5m'));
|
|
465
492
|
*/
|
|
466
493
|
async extendTimeout(
|
|
467
494
|
duration: number,
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Autogenerated by inject-version.ts
|
|
2
|
-
export const VERSION = "1.0
|
|
2
|
+
export const VERSION = "1.1.0";
|