@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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +61 -12
- package/dist/api-client/api-client.d.ts +72 -6
- package/dist/api-client/api-client.js +25 -13
- package/dist/api-client/index.d.ts +1 -0
- package/dist/api-client/index.js +15 -0
- package/dist/api-client/validators.d.ts +343 -50
- package/dist/api-client/validators.js +37 -16
- package/dist/command.d.ts +18 -5
- package/dist/command.js +27 -6
- package/dist/sandbox.d.ts +30 -21
- package/dist/sandbox.js +40 -14
- package/dist/utils/consume-readable.d.ts +5 -0
- package/dist/utils/consume-readable.js +15 -0
- package/dist/utils/resolveSignal.d.ts +13 -0
- package/dist/utils/resolveSignal.js +21 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/api-client/api-client.ts +56 -23
- package/src/api-client/index.ts +1 -0
- package/src/api-client/validators.ts +45 -15
- package/src/command.test.ts +39 -16
- package/src/command.ts +33 -10
- package/src/sandbox.test.ts +29 -0
- package/src/sandbox.ts +66 -26
- package/src/utils/consume-readable.ts +12 -0
- package/src/utils/resolveSignal.ts +24 -0
- package/src/version.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
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
|
|
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
|
|
13
|
-
APIs are enabled for your team.
|
|
12
|
+
Vercel Sandbox is in [beta](https://vercel.com/docs/release-phases#beta).
|
|
14
13
|
|
|
15
|
-
|
|
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
|
-
|
|
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`
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
71
|
-
: (0, base_client_1.parseOrThrow)(validators_1.
|
|
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.
|
|
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:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
98
|
-
await (0, base_client_1.parseOrThrow)(validators_1.
|
|
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.
|
|
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;
|
package/dist/api-client/index.js
CHANGED
|
@@ -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);
|