@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.
Files changed (63) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +18 -0
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/CHANGELOG.md +22 -0
  5. package/LICENSE +21 -0
  6. package/README.md +4 -4
  7. package/dist/api-client/api-client.d.ts +11 -3
  8. package/dist/api-client/api-client.js +6 -1
  9. package/dist/api-client/api-client.js.map +1 -0
  10. package/dist/api-client/api-error.js +1 -0
  11. package/dist/api-client/api-error.js.map +1 -0
  12. package/dist/api-client/base-client.d.ts +1 -0
  13. package/dist/api-client/base-client.js +2 -1
  14. package/dist/api-client/base-client.js.map +1 -0
  15. package/dist/api-client/file-writer.js +1 -0
  16. package/dist/api-client/file-writer.js.map +1 -0
  17. package/dist/api-client/index.js +1 -0
  18. package/dist/api-client/index.js.map +1 -0
  19. package/dist/api-client/validators.d.ts +23 -0
  20. package/dist/api-client/validators.js +2 -0
  21. package/dist/api-client/validators.js.map +1 -0
  22. package/dist/api-client/with-retry.js +11 -0
  23. package/dist/api-client/with-retry.js.map +1 -0
  24. package/dist/command.js +1 -0
  25. package/dist/command.js.map +1 -0
  26. package/dist/constants.d.ts +1 -0
  27. package/dist/constants.js +3 -0
  28. package/dist/constants.js.map +1 -0
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/sandbox.d.ts +14 -5
  32. package/dist/sandbox.js +27 -5
  33. package/dist/sandbox.js.map +1 -0
  34. package/dist/utils/array.js +1 -0
  35. package/dist/utils/array.js.map +1 -0
  36. package/dist/utils/consume-readable.js +1 -0
  37. package/dist/utils/consume-readable.js.map +1 -0
  38. package/dist/utils/decode-base64-url.js +1 -0
  39. package/dist/utils/decode-base64-url.js.map +1 -0
  40. package/dist/utils/get-credentials.js +1 -0
  41. package/dist/utils/get-credentials.js.map +1 -0
  42. package/dist/utils/jwt-expiry.js +1 -0
  43. package/dist/utils/jwt-expiry.js.map +1 -0
  44. package/dist/utils/normalizePath.js +1 -0
  45. package/dist/utils/normalizePath.js.map +1 -0
  46. package/dist/utils/resolveSignal.js +1 -0
  47. package/dist/utils/resolveSignal.js.map +1 -0
  48. package/dist/utils/types.js +1 -0
  49. package/dist/utils/types.js.map +1 -0
  50. package/dist/version.d.ts +1 -1
  51. package/dist/version.js +2 -1
  52. package/dist/version.js.map +1 -0
  53. package/package.json +2 -2
  54. package/src/api-client/api-client.ts +20 -4
  55. package/src/api-client/base-client.ts +7 -2
  56. package/src/api-client/validators.ts +1 -0
  57. package/src/api-client/with-retry.ts +11 -0
  58. package/src/command.test.ts +81 -77
  59. package/src/constants.ts +1 -0
  60. package/src/sandbox.test.ts +60 -58
  61. package/src/sandbox.ts +37 -10
  62. package/src/version.ts +1 -1
  63. package/tsconfig.json +1 -0
@@ -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
- const PORTS = [3000, 4000];
8
- let sandbox: Sandbox;
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
- sandbox = await Sandbox.create({ ports: PORTS });
12
- });
11
+ beforeEach(async () => {
12
+ sandbox = await Sandbox.create({ ports: PORTS });
13
+ });
13
14
 
14
- afterEach(async () => {
15
- await sandbox.stop();
16
- });
15
+ afterEach(async () => {
16
+ await sandbox.stop();
17
+ });
17
18
 
18
- it("allows to write files and then read them", async () => {
19
- await sandbox.writeFiles([
20
- { path: "hello1.txt", content: Buffer.from("Hello 1") },
21
- { path: "hello2.txt", content: Buffer.from("Hello 2") },
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
- const content1 = await sandbox.readFile({ path: "hello1.txt" });
25
- const content2 = await sandbox.readFile({ path: "hello2.txt" });
26
- expect((await consumeReadable(content1!)).toString()).toBe("Hello 1");
27
- expect((await consumeReadable(content2!)).toString()).toBe("Hello 2");
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
- const serverScript = `
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
- await sandbox.writeFiles([
48
- { path: "server.js", content: Buffer.from(serverScript) },
49
- ]);
48
+ await sandbox.writeFiles([
49
+ { path: "server.js", content: Buffer.from(serverScript) },
50
+ ]);
50
51
 
51
- const server = await sandbox.runCommand({
52
- cmd: "node",
53
- args: ["server.js", ...PORTS.map(String)],
54
- detached: true,
55
- stdout: process.stdout,
56
- stderr: process.stderr,
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
- await new Promise((resolve) => setTimeout(resolve, 1000));
60
+ await new Promise((resolve) => setTimeout(resolve, 1000));
60
61
 
61
- for (const port of PORTS) {
62
- const response = await fetch(sandbox.domain(port));
63
- const text = await response.text();
64
- expect(text).toBe(`hello port ${port}`);
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
- await server.kill();
68
- });
68
+ await server.kill();
69
+ });
69
70
 
70
- it("allows extending the sandbox timeout", async () => {
71
- const originalTimeout = sandbox.timeout;
72
- const extensionDuration = ms("5m");
71
+ it("allows extending the sandbox timeout", async () => {
72
+ const originalTimeout = sandbox.timeout;
73
+ const extensionDuration = ms("5m");
73
74
 
74
- await sandbox.extendTimeout(extensionDuration);
75
- expect(sandbox.timeout).toEqual(originalTimeout + extensionDuration);
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
- try {
80
- await sandbox.extendTimeout(ms("5d"));
81
- expect.fail("Expected extendTimeout to throw an error");
82
- } catch (error) {
83
- expect(error).toBeInstanceOf(APIError);
84
- expect(error).toMatchObject({
85
- response: { status: 400 },
86
- json: {
87
- error: { code: "sandbox_timeout_invalid" },
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?: "node22" | "python3.13" | (string & {});
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] & Partial<Credentials>,
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
- for await (const log of command.logs({ signal: params.signal })) {
355
- if (log.stream === "stdout") {
356
- params.stdout?.write(log.data);
357
- } else if (log.stream === "stderr") {
358
- params.stderr?.write(log.data);
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.3";
2
+ export const VERSION = "1.1.0";
package/tsconfig.json CHANGED
@@ -6,6 +6,7 @@
6
6
  "strict": true,
7
7
  "target": "ES2020",
8
8
  "declaration": true,
9
+ "sourceMap": true,
9
10
  "module": "commonjs",
10
11
  "resolveJsonModule": true
11
12
  },