@vercel/sandbox 0.0.9 → 0.0.11

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @vercel/sandbox@0.0.9 build /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
2
+ > @vercel/sandbox@0.0.11 build /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
3
3
  > tsc
4
4
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @vercel/sandbox@0.0.9 typecheck /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
2
+ > @vercel/sandbox@0.0.11 typecheck /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
3
3
  > tsc --noEmit
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @vercel/sandbox
2
2
 
3
+ ## 0.0.11
4
+
5
+ ### Patch Changes
6
+
7
+ - Rename `stream` to `content` when writing files ([#60](https://github.com/vercel/sandbox-sdk/pull/60))
8
+
9
+ ## 0.0.10
10
+
11
+ ### Patch Changes
12
+
13
+ - Add `readFile` and fix a bug writing files to a Sandbox ([#54](https://github.com/vercel/sandbox-sdk/pull/54))
14
+
15
+ - Remove unused `routes` parameter from getSandbox ([#59](https://github.com/vercel/sandbox-sdk/pull/59))
16
+
3
17
  ## 0.0.9
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  Vercel Sandbox allows you to run arbitrary code in isolated, ephemeral Linux
2
- VMs. This product is in private beta.
2
+ VMs. This product is in [beta](https://vercel.com/docs/release-phases#beta).
3
3
 
4
4
  ## What is a sandbox?
5
5
 
@@ -9,14 +9,9 @@ infrastructure][hive] that powers 1M+ builds a day at Vercel.
9
9
 
10
10
  ## Getting started
11
11
 
12
- Vercel Sandbox is in private beta. These examples will not work unless these
13
- APIs are enabled for your team.
12
+ Vercel Sandbox is in [beta](https://vercel.com/docs/release-phases#beta).
14
13
 
15
- - Go to your team settings, and copy the team ID.
16
- - Go to a project's settings, and copy the project ID.
17
- - Go to your Vercel account settings and [create a token][create-token]. Make
18
- sure it is scoped to the team ID from the previous step.
19
- - Create a new project:
14
+ To get started, create a new project:
20
15
 
21
16
  ```sh
22
17
  mkdir sandbox-test
@@ -26,6 +21,18 @@ pnpm add @vercel/sandbox ms
26
21
  pnpm add -D @types/ms @types/node
27
22
  ```
28
23
 
24
+ Link it to Vercel:
25
+
26
+ ```sh
27
+ vercel link
28
+ ```
29
+
30
+ Pull its environment variables:
31
+
32
+ ```sh
33
+ vercel env pull
34
+ ```
35
+
29
36
  Now create `next-dev.ts`:
30
37
 
31
38
  {@includeCode ../cli/src/example-next.ts}
@@ -33,7 +40,7 @@ Now create `next-dev.ts`:
33
40
  Run it like this:
34
41
 
35
42
  ```sh
36
- VERCEL_TEAM_ID=<team_id> VERCEL_TOKEN=<token> VERCEL_PROJECT_ID=<project_id> node --experimental-strip-types ./next-dev.ts
43
+ node --env-file .env.local --experimental-strip-types ./next-dev.ts
37
44
  ```
38
45
 
39
46
  This will:
@@ -45,12 +52,53 @@ This will:
45
52
 
46
53
  All while streaming logs to your local terminal.
47
54
 
55
+ ## Authentication
56
+
57
+ ### Vercel OIDC token
58
+
59
+ The SDK uses Vercel OIDC tokens to authenticate whenever available. This is the
60
+ most straightforward and recommended way to authenticate.
61
+
62
+ When developing locally, you can download a development token to `.env.local`
63
+ using `vercel env pull`. After 12 hours the development token expires, meaning
64
+ you will have to call `vercel env pull` again.
65
+
66
+ In production, Vercel manages token expiration for you.
67
+
68
+ ### Access token
69
+
70
+ If you want to use the SDK from an environment where `VERCEL_OIDC_TOKEN` is
71
+ unavailable, you can also authenticate using an access token:
72
+
73
+ - Go to your team settings, and copy the team ID.
74
+ - Go to a project's settings, and copy the project ID.
75
+ - Go to your Vercel account settings and [create a token][create-token]. Make
76
+ sure it is scoped to the team ID from the previous step.
77
+
78
+ Set your team ID, project ID, and token to the environment variables
79
+ `VERCEL_TEAM_ID`, `VERCEL_PROJECT_ID`, and `VERCEL_TOKEN`. Then pass these to
80
+ the `create` method:
81
+
82
+ ```ts
83
+ const sandbox = await Sandbox.create({
84
+ teamId: process.env.VERCEL_TEAM_ID!,
85
+ projectId: process.env.VERCEL_PROJECT_ID!,
86
+ token: process.env.VERCEL_TOKEN!,
87
+ source: {
88
+ url: "https://github.com/vercel/sandbox-example-next.git",
89
+ type: "git",
90
+ },
91
+ resources: { vcpus: 4 },
92
+ timeout: ms("5m"),
93
+ ports: [3000],
94
+ runtime: "node22",
95
+ });
96
+ ```
97
+
48
98
  ## Limitations
49
99
 
50
- - Sandbox only supports cloning public repositories at the moment.
51
100
  - `sudo` is not available. User code is executed as the `vercel-sandbox` user.
52
101
  - Max resources: 8 vCPUs. You will get 2048 MB of memory per vCPU.
53
- - All APIs of the SDK are subject to change. **We make no stability promises.**
54
102
 
55
103
  ## System
56
104
 
@@ -78,7 +126,8 @@ whois
78
126
  zstd
79
127
  ```
80
128
 
81
- - The `node22` system ships a Node 22 runtime under `/vercel/runtimes/node22`.
129
+ - The `node22` image ships a Node 22 runtime under `/vercel/runtimes/node22`.
130
+ - The `python3.13` image ships a Python 3.13 runtime under `/vercel/runtimes/python`.
82
131
  - User code is executed as the `vercel-sandbox` user.
83
132
  - `/vercel/sandbox` is writable.
84
133
 
@@ -43,6 +43,8 @@ export declare class APIClient extends BaseClient {
43
43
  url: string;
44
44
  depth?: number;
45
45
  revision?: string;
46
+ username?: string;
47
+ password?: string;
46
48
  } | {
47
49
  type: "tarball";
48
50
  url: string;
@@ -117,7 +119,7 @@ export declare class APIClient extends BaseClient {
117
119
  sandboxId: string;
118
120
  files: {
119
121
  path: string;
120
- stream: Buffer;
122
+ content: Buffer;
121
123
  }[];
122
124
  }): Promise<void>;
123
125
  readFile(params: {
@@ -10,6 +10,7 @@ const api_error_1 = require("./api-error");
10
10
  const file_writer_1 = require("./file-writer");
11
11
  const lru_cache_1 = require("lru-cache");
12
12
  const version_1 = require("../version");
13
+ const consume_readable_1 = require("../utils/consume-readable");
13
14
  const jsonlines_1 = __importDefault(require("jsonlines"));
14
15
  const os_1 = __importDefault(require("os"));
15
16
  const ms_1 = __importDefault(require("ms"));
@@ -82,11 +83,13 @@ class APIClient extends base_client_1.BaseClient {
82
83
  getFileWriter(params) {
83
84
  const writer = new file_writer_1.FileWriter();
84
85
  return {
85
- response: this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
86
- method: "POST",
87
- headers: { "content-type": "application/gzip" },
88
- body: writer.readable,
89
- }),
86
+ response: (async () => {
87
+ return this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
88
+ method: "POST",
89
+ headers: { "content-type": "application/gzip" },
90
+ body: await (0, consume_readable_1.consumeReadable)(writer.readable),
91
+ });
92
+ })(),
90
93
  writer,
91
94
  };
92
95
  }
@@ -95,9 +98,9 @@ class APIClient extends base_client_1.BaseClient {
95
98
  sandboxId: params.sandboxId,
96
99
  });
97
100
  for (const file of params.files) {
98
- await writer.addFile({ name: file.path, content: file.stream });
101
+ await writer.addFile({ name: file.path, content: file.content });
99
102
  }
100
- await writer.end();
103
+ writer.end();
101
104
  await (0, base_client_1.parseOrThrow)(validators_1.EmptyResponse, await response);
102
105
  }
103
106
  async readFile(params) {
package/dist/sandbox.d.ts CHANGED
@@ -19,6 +19,13 @@ export interface CreateSandboxParams {
19
19
  url: string;
20
20
  depth?: number;
21
21
  revision?: string;
22
+ } | {
23
+ type: "git";
24
+ url: string;
25
+ username: string;
26
+ password: string;
27
+ depth?: number;
28
+ revision?: string;
22
29
  } | {
23
30
  type: "tarball";
24
31
  url: string;
@@ -48,13 +55,6 @@ export interface CreateSandboxParams {
48
55
  }
49
56
  /** @inline */
50
57
  interface GetSandboxParams {
51
- /**
52
- * Port-to-subdomain route mappings.
53
- */
54
- routes: Array<{
55
- subdomain: string;
56
- port: number;
57
- }>;
58
58
  /**
59
59
  * Unique identifier of the sandbox.
60
60
  */
@@ -183,6 +183,16 @@ export declare class Sandbox {
183
183
  * @param path - Path of the directory to create
184
184
  */
185
185
  mkDir(path: string): Promise<void>;
186
+ /**
187
+ * Read a file from the filesystem of this sandbox.
188
+ *
189
+ * @param file - File to read, with path and optional cwd
190
+ * @returns A promise that resolves to a ReadableStream containing the file contents
191
+ */
192
+ readFile(file: {
193
+ path: string;
194
+ cwd?: string;
195
+ }): Promise<NodeJS.ReadableStream | null>;
186
196
  /**
187
197
  * Write files to the filesystem of this sandbox.
188
198
  *
@@ -191,7 +201,7 @@ export declare class Sandbox {
191
201
  */
192
202
  writeFiles(files: {
193
203
  path: string;
194
- stream: Buffer;
204
+ content: Buffer;
195
205
  }[]): Promise<void>;
196
206
  /**
197
207
  * Get the public domain of a port of this sandbox.
package/dist/sandbox.js CHANGED
@@ -143,6 +143,19 @@ class Sandbox {
143
143
  path: path,
144
144
  });
145
145
  }
146
+ /**
147
+ * Read a file from the filesystem of this sandbox.
148
+ *
149
+ * @param file - File to read, with path and optional cwd
150
+ * @returns A promise that resolves to a ReadableStream containing the file contents
151
+ */
152
+ async readFile(file) {
153
+ return this.client.readFile({
154
+ sandboxId: this.sandbox.id,
155
+ path: file.path,
156
+ cwd: file.cwd,
157
+ });
158
+ }
146
159
  /**
147
160
  * Write files to the filesystem of this sandbox.
148
161
  *
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Consumes a readable entirely concatenating all content in a single Buffer
3
+ * @param readable A Readable stream
4
+ */
5
+ export declare function consumeReadable(readable: NodeJS.ReadableStream): Promise<Buffer<ArrayBufferLike>>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.consumeReadable = consumeReadable;
4
+ /**
5
+ * Consumes a readable entirely concatenating all content in a single Buffer
6
+ * @param readable A Readable stream
7
+ */
8
+ function consumeReadable(readable) {
9
+ return new Promise((resolve, reject) => {
10
+ const chunks = [];
11
+ readable.on("error", (err) => reject(err));
12
+ readable.on("data", (chunk) => chunks.push(chunk));
13
+ readable.on("end", () => resolve(Buffer.concat(chunks)));
14
+ });
15
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.0.9";
1
+ export declare const VERSION = "0.0.11";
package/dist/version.js CHANGED
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // Autogenerated by inject-version.ts
5
- exports.VERSION = "0.0.9";
5
+ exports.VERSION = "0.0.11";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/sandbox",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,6 +16,7 @@ import { APIError } from "./api-error";
16
16
  import { FileWriter } from "./file-writer";
17
17
  import { LRUCache } from "lru-cache";
18
18
  import { VERSION } from "../version";
19
+ import { consumeReadable } from "../utils/consume-readable";
19
20
  import { z } from "zod";
20
21
  import jsonlines from "jsonlines";
21
22
  import os from "os";
@@ -66,7 +67,14 @@ export class APIClient extends BaseClient {
66
67
  ports?: number[];
67
68
  projectId: string;
68
69
  source?:
69
- | { type: "git"; url: string; depth?: number; revision?: string }
70
+ | {
71
+ type: "git";
72
+ url: string;
73
+ depth?: number;
74
+ revision?: string;
75
+ username?: string;
76
+ password?: string;
77
+ }
70
78
  | { type: "tarball"; url: string };
71
79
  timeout?: number;
72
80
  resources?: { vcpus: number };
@@ -153,28 +161,30 @@ export class APIClient extends BaseClient {
153
161
  getFileWriter(params: { sandboxId: string }) {
154
162
  const writer = new FileWriter();
155
163
  return {
156
- response: this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
157
- method: "POST",
158
- headers: { "content-type": "application/gzip" },
159
- body: writer.readable,
160
- }),
164
+ response: (async () => {
165
+ return this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
166
+ method: "POST",
167
+ headers: { "content-type": "application/gzip" },
168
+ body: await consumeReadable(writer.readable),
169
+ });
170
+ })(),
161
171
  writer,
162
172
  };
163
173
  }
164
174
 
165
175
  async writeFiles(params: {
166
176
  sandboxId: string;
167
- files: { path: string; stream: Buffer }[];
177
+ files: { path: string; content: Buffer }[];
168
178
  }) {
169
179
  const { writer, response } = this.getFileWriter({
170
180
  sandboxId: params.sandboxId,
171
181
  });
172
182
 
173
183
  for (const file of params.files) {
174
- await writer.addFile({ name: file.path, content: file.stream });
184
+ await writer.addFile({ name: file.path, content: file.content });
175
185
  }
176
186
 
177
- await writer.end();
187
+ writer.end();
178
188
  await parseOrThrow(EmptyResponse, await response);
179
189
  }
180
190
 
@@ -0,0 +1,29 @@
1
+ import { it, beforeEach, afterEach, expect } from "vitest";
2
+ import { consumeReadable } from "./utils/consume-readable";
3
+ import { Sandbox } from "./sandbox";
4
+
5
+ let sandbox: Sandbox;
6
+
7
+ beforeEach(async () => {
8
+ sandbox = await Sandbox.create({
9
+ projectId: process.env.VERCEL_PROJECT_ID!,
10
+ teamId: process.env.VERCEL_TEAM_ID!,
11
+ token: process.env.VERCEL_TOKEN!,
12
+ });
13
+ });
14
+
15
+ afterEach(async () => {
16
+ await sandbox.stop();
17
+ });
18
+
19
+ it("allows to write files and then read them", async () => {
20
+ await sandbox.writeFiles([
21
+ { path: "hello1.txt", stream: Buffer.from("Hello 1") },
22
+ { path: "hello2.txt", stream: Buffer.from("Hello 2") },
23
+ ]);
24
+
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
+ });
package/src/sandbox.ts CHANGED
@@ -16,7 +16,20 @@ export interface CreateSandboxParams {
16
16
  * - `revision`: Clones and checks out a specific commit, branch, or tag
17
17
  */
18
18
  source?:
19
- | { type: "git"; url: string; depth?: number; revision?: string }
19
+ | {
20
+ type: "git";
21
+ url: string;
22
+ depth?: number;
23
+ revision?: string;
24
+ }
25
+ | {
26
+ type: "git";
27
+ url: string;
28
+ username: string;
29
+ password: string;
30
+ depth?: number;
31
+ revision?: string;
32
+ }
20
33
  | { type: "tarball"; url: string };
21
34
  /**
22
35
  * Array of port numbers to expose from the sandbox.
@@ -43,10 +56,6 @@ export interface CreateSandboxParams {
43
56
 
44
57
  /** @inline */
45
58
  interface GetSandboxParams {
46
- /**
47
- * Port-to-subdomain route mappings.
48
- */
49
- routes: Array<{ subdomain: string; port: number }>;
50
59
  /**
51
60
  * Unique identifier of the sandbox.
52
61
  */
@@ -294,13 +303,27 @@ export class Sandbox {
294
303
  });
295
304
  }
296
305
 
306
+ /**
307
+ * Read a file from the filesystem of this sandbox.
308
+ *
309
+ * @param file - File to read, with path and optional cwd
310
+ * @returns A promise that resolves to a ReadableStream containing the file contents
311
+ */
312
+ async readFile(file: { path: string; cwd?: string }) {
313
+ return this.client.readFile({
314
+ sandboxId: this.sandbox.id,
315
+ path: file.path,
316
+ cwd: file.cwd,
317
+ });
318
+ }
319
+
297
320
  /**
298
321
  * Write files to the filesystem of this sandbox.
299
322
  *
300
323
  * @param files - Array of files with path and stream/buffer contents
301
324
  * @returns A promise that resolves when the files are written
302
325
  */
303
- async writeFiles(files: { path: string; stream: Buffer }[]) {
326
+ async writeFiles(files: { path: string; content: Buffer }[]) {
304
327
  return this.client.writeFiles({
305
328
  sandboxId: this.sandbox.id,
306
329
  files: files,
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Consumes a readable entirely concatenating all content in a single Buffer
3
+ * @param readable A Readable stream
4
+ */
5
+ export function consumeReadable(readable: NodeJS.ReadableStream) {
6
+ return new Promise<Buffer>((resolve, reject) => {
7
+ const chunks: Buffer[] = [];
8
+ readable.on("error", (err) => reject(err));
9
+ readable.on("data", (chunk) => chunks.push(chunk));
10
+ readable.on("end", () => resolve(Buffer.concat(chunks)));
11
+ });
12
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Autogenerated by inject-version.ts
2
- export const VERSION = "0.0.9";
2
+ export const VERSION = "0.0.11";