@vercel/sandbox 0.0.8 → 0.0.10

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.8 build /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
2
+ > @vercel/sandbox@0.0.10 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.8 typecheck /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
2
+ > @vercel/sandbox@0.0.10 typecheck /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
3
3
  > tsc --noEmit
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @vercel/sandbox
2
2
 
3
+ ## 0.0.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Add `readFile` and fix a bug writing files to a Sandbox ([#54](https://github.com/vercel/sandbox-sdk/pull/54))
8
+
9
+ - Remove unused `routes` parameter from getSandbox ([#59](https://github.com/vercel/sandbox-sdk/pull/59))
10
+
11
+ ## 0.0.9
12
+
13
+ ### Patch Changes
14
+
15
+ - Add `cmd.kill()` to stop/signal commands ([#48](https://github.com/vercel/sandbox-sdk/pull/48))
16
+ - Update SDK to use the new API ([#51](https://github.com/vercel/sandbox-sdk/pull/51))
17
+
3
18
  ## 0.0.8
4
19
 
5
20
  ### 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
 
@@ -1,5 +1,5 @@
1
1
  import { BaseClient, type Parsed, type RequestParams } from "./base-client";
2
- import { Command, CommandFinished, LogLine, StoppedSandbox } from "./validators";
2
+ import { SandboxResponse, CommandResponse, CommandFinishedResponse, LogLine } from "./validators";
3
3
  import { FileWriter } from "./file-writer";
4
4
  import { z } from "zod";
5
5
  export declare class APIClient extends BaseClient {
@@ -10,6 +10,31 @@ export declare class APIClient extends BaseClient {
10
10
  token: string;
11
11
  });
12
12
  protected request(path: string, params?: RequestParams): Promise<import("node-fetch").Response>;
13
+ getSandbox(params: {
14
+ sandboxId: string;
15
+ }): Promise<Parsed<{
16
+ sandbox: {
17
+ region: string;
18
+ timeout: number;
19
+ status: "pending" | "running" | "stopping" | "stopped" | "failed";
20
+ id: string;
21
+ memory: number;
22
+ vcpus: number;
23
+ runtime: string;
24
+ requestedAt: number;
25
+ createdAt: number;
26
+ updatedAt: number;
27
+ duration?: number | undefined;
28
+ startedAt?: number | undefined;
29
+ requestedStopAt?: number | undefined;
30
+ stoppedAt?: number | undefined;
31
+ };
32
+ routes: {
33
+ port: number;
34
+ url: string;
35
+ subdomain: string;
36
+ }[];
37
+ }>>;
13
38
  createSandbox(params: {
14
39
  ports?: number[];
15
40
  projectId: string;
@@ -18,6 +43,8 @@ export declare class APIClient extends BaseClient {
18
43
  url: string;
19
44
  depth?: number;
20
45
  revision?: string;
46
+ username?: string;
47
+ password?: string;
21
48
  } | {
22
49
  type: "tarball";
23
50
  url: string;
@@ -28,9 +55,25 @@ export declare class APIClient extends BaseClient {
28
55
  };
29
56
  runtime?: "node22" | "python3.13";
30
57
  }): Promise<Parsed<{
31
- sandboxId: string;
58
+ sandbox: {
59
+ region: string;
60
+ timeout: number;
61
+ status: "pending" | "running" | "stopping" | "stopped" | "failed";
62
+ id: string;
63
+ memory: number;
64
+ vcpus: number;
65
+ runtime: string;
66
+ requestedAt: number;
67
+ createdAt: number;
68
+ updatedAt: number;
69
+ duration?: number | undefined;
70
+ startedAt?: number | undefined;
71
+ requestedStopAt?: number | undefined;
72
+ stoppedAt?: number | undefined;
73
+ };
32
74
  routes: {
33
75
  port: number;
76
+ url: string;
34
77
  subdomain: string;
35
78
  }[];
36
79
  }>>;
@@ -41,18 +84,26 @@ export declare class APIClient extends BaseClient {
41
84
  args: string[];
42
85
  env: Record<string, string>;
43
86
  }): Promise<Parsed<{
44
- cmdId: string;
87
+ command: {
88
+ name: string;
89
+ cwd: string;
90
+ args: string[];
91
+ id: string;
92
+ startedAt: number;
93
+ sandboxId: string;
94
+ exitCode: number | null;
95
+ };
45
96
  }>>;
46
97
  getCommand(params: {
47
98
  sandboxId: string;
48
99
  cmdId: string;
49
100
  wait: true;
50
- }): Promise<Parsed<z.infer<typeof CommandFinished>>>;
101
+ }): Promise<Parsed<z.infer<typeof CommandFinishedResponse>>>;
51
102
  getCommand(params: {
52
103
  sandboxId: string;
53
104
  cmdId: string;
54
105
  wait?: boolean;
55
- }): Promise<Parsed<z.infer<typeof Command>>>;
106
+ }): Promise<Parsed<z.infer<typeof CommandResponse>>>;
56
107
  mkDir(params: {
57
108
  sandboxId: string;
58
109
  path: string;
@@ -76,11 +127,26 @@ export declare class APIClient extends BaseClient {
76
127
  path: string;
77
128
  cwd?: string;
78
129
  }): Promise<NodeJS.ReadableStream | null>;
130
+ killCommand(params: {
131
+ sandboxId: string;
132
+ commandId: string;
133
+ signal: number;
134
+ }): Promise<Parsed<{
135
+ command: {
136
+ name: string;
137
+ cwd: string;
138
+ args: string[];
139
+ id: string;
140
+ startedAt: number;
141
+ sandboxId: string;
142
+ exitCode: number | null;
143
+ };
144
+ }>>;
79
145
  getLogs(params: {
80
146
  sandboxId: string;
81
147
  cmdId: string;
82
148
  }): AsyncIterable<z.infer<typeof LogLine>>;
83
149
  stopSandbox(params: {
84
150
  sandboxId: string;
85
- }): Promise<Parsed<z.infer<typeof StoppedSandbox>>>;
151
+ }): Promise<Parsed<z.infer<typeof SandboxResponse>>>;
86
152
  }
@@ -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"));
@@ -41,8 +42,11 @@ class APIClient extends base_client_1.BaseClient {
41
42
  },
42
43
  });
43
44
  }
45
+ async getSandbox(params) {
46
+ return (0, base_client_1.parseOrThrow)(validators_1.SandboxAndRoutesResponse, await this.request(`/v1/sandboxes/${params.sandboxId}`));
47
+ }
44
48
  async createSandbox(params) {
45
- return (0, base_client_1.parseOrThrow)(validators_1.CreatedSandbox, await this.request("/v1/sandboxes", {
49
+ return (0, base_client_1.parseOrThrow)(validators_1.SandboxAndRoutesResponse, await this.request("/v1/sandboxes", {
46
50
  method: "POST",
47
51
  body: JSON.stringify({
48
52
  projectId: params.projectId,
@@ -55,7 +59,7 @@ class APIClient extends base_client_1.BaseClient {
55
59
  }));
56
60
  }
57
61
  async runCommand(params) {
58
- return (0, base_client_1.parseOrThrow)(validators_1.CreatedCommand, await this.request(`/v1/sandboxes/${params.sandboxId}/cmd`, {
62
+ return (0, base_client_1.parseOrThrow)(validators_1.CommandResponse, await this.request(`/v1/sandboxes/${params.sandboxId}/cmd`, {
59
63
  method: "POST",
60
64
  body: JSON.stringify({
61
65
  command: params.command,
@@ -67,11 +71,11 @@ class APIClient extends base_client_1.BaseClient {
67
71
  }
68
72
  async getCommand(params) {
69
73
  return params.wait
70
- ? (0, base_client_1.parseOrThrow)(validators_1.CommandFinished, await this.request(`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`, { query: { wait: "true" } }))
71
- : (0, base_client_1.parseOrThrow)(validators_1.Command, await this.request(`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`));
74
+ ? (0, base_client_1.parseOrThrow)(validators_1.CommandFinishedResponse, await this.request(`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`, { query: { wait: "true" } }))
75
+ : (0, base_client_1.parseOrThrow)(validators_1.CommandResponse, await this.request(`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`));
72
76
  }
73
77
  async mkDir(params) {
74
- return (0, base_client_1.parseOrThrow)(validators_1.WrittenFile, await this.request(`/v1/sandboxes/${params.sandboxId}/fs/mkdir`, {
78
+ return (0, base_client_1.parseOrThrow)(validators_1.EmptyResponse, await this.request(`/v1/sandboxes/${params.sandboxId}/fs/mkdir`, {
75
79
  method: "POST",
76
80
  body: JSON.stringify({ path: params.path, cwd: params.cwd }),
77
81
  }));
@@ -79,11 +83,13 @@ class APIClient extends base_client_1.BaseClient {
79
83
  getFileWriter(params) {
80
84
  const writer = new file_writer_1.FileWriter();
81
85
  return {
82
- response: this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
83
- method: "POST",
84
- headers: { "content-type": "application/gzip" },
85
- body: writer.readable,
86
- }),
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
+ })(),
87
93
  writer,
88
94
  };
89
95
  }
@@ -94,8 +100,8 @@ class APIClient extends base_client_1.BaseClient {
94
100
  for (const file of params.files) {
95
101
  await writer.addFile({ name: file.path, content: file.stream });
96
102
  }
97
- await writer.end();
98
- await (0, base_client_1.parseOrThrow)(validators_1.WrittenFile, await response);
103
+ writer.end();
104
+ await (0, base_client_1.parseOrThrow)(validators_1.EmptyResponse, await response);
99
105
  }
100
106
  async readFile(params) {
101
107
  const response = await this.request(`/v1/sandboxes/${params.sandboxId}/fs/read`, {
@@ -107,6 +113,12 @@ class APIClient extends base_client_1.BaseClient {
107
113
  }
108
114
  return response.body;
109
115
  }
116
+ async killCommand(params) {
117
+ return (0, base_client_1.parseOrThrow)(validators_1.CommandResponse, await this.request(`/v1/sandboxes/${params.sandboxId}/${params.commandId}/kill`, {
118
+ method: "POST",
119
+ body: JSON.stringify({ signal: params.signal }),
120
+ }));
121
+ }
110
122
  async *getLogs(params) {
111
123
  const url = `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}/logs`;
112
124
  const response = await this.request(url, { method: "GET" });
@@ -133,7 +145,7 @@ class APIClient extends base_client_1.BaseClient {
133
145
  }
134
146
  async stopSandbox(params) {
135
147
  const url = `/v1/sandboxes/${params.sandboxId}/stop`;
136
- return (0, base_client_1.parseOrThrow)(validators_1.StoppedSandbox, await this.request(url, { method: "POST" }));
148
+ return (0, base_client_1.parseOrThrow)(validators_1.SandboxResponse, await this.request(url, { method: "POST" }));
137
149
  }
138
150
  }
139
151
  exports.APIClient = APIClient;
@@ -1 +1,2 @@
1
1
  export { APIClient } from "./api-client";
2
+ export * from "./validators";
@@ -1,5 +1,20 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
17
  exports.APIClient = void 0;
4
18
  var api_client_1 = require("./api-client");
5
19
  Object.defineProperty(exports, "APIClient", { enumerable: true, get: function () { return api_client_1.APIClient; } });
20
+ __exportStar(require("./validators"), exports);