@vercel/sandbox 0.0.9 → 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 +8 -0
- package/README.md +61 -12
- package/dist/api-client/api-client.d.ts +2 -0
- package/dist/api-client/api-client.js +9 -6
- package/dist/sandbox.d.ts +17 -7
- package/dist/sandbox.js +13 -0
- package/dist/utils/consume-readable.d.ts +5 -0
- package/dist/utils/consume-readable.js +15 -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 +17 -7
- package/src/sandbox.test.ts +29 -0
- package/src/sandbox.ts +28 -5
- package/src/utils/consume-readable.ts +12 -0
- package/src/version.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
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
|
+
|
|
3
11
|
## 0.0.9
|
|
4
12
|
|
|
5
13
|
### 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
|
|
|
@@ -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:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
}
|
|
@@ -97,7 +100,7 @@ class APIClient extends base_client_1.BaseClient {
|
|
|
97
100
|
for (const file of params.files) {
|
|
98
101
|
await writer.addFile({ name: file.path, content: file.stream });
|
|
99
102
|
}
|
|
100
|
-
|
|
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
|
*
|
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,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.
|
|
1
|
+
export declare const VERSION = "0.0.10";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -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
|
-
| {
|
|
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,11 +161,13 @@ export class APIClient extends BaseClient {
|
|
|
153
161
|
getFileWriter(params: { sandboxId: string }) {
|
|
154
162
|
const writer = new FileWriter();
|
|
155
163
|
return {
|
|
156
|
-
response:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
}
|
|
@@ -174,7 +184,7 @@ export class APIClient extends BaseClient {
|
|
|
174
184
|
await writer.addFile({ name: file.path, content: file.stream });
|
|
175
185
|
}
|
|
176
186
|
|
|
177
|
-
|
|
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
|
-
| {
|
|
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,6 +303,20 @@ 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
|
*
|
|
@@ -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.
|
|
2
|
+
export const VERSION = "0.0.10";
|