@vercel/sandbox 0.0.7 → 0.0.9

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/dist/sandbox.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { Readable, type Writable } from "stream";
1
+ import type { SandboxData, SandboxRouteData } from "./api-client";
2
+ import type { Writable } from "stream";
2
3
  import { APIClient } from "./api-client";
3
- import { Command, CommandFinished } from "./command";
4
+ import { Command, type CommandFinished } from "./command";
4
5
  import { type Credentials } from "./utils/get-credentials";
5
6
  /** @inline */
6
7
  export interface CreateSandboxParams {
@@ -8,10 +9,16 @@ export interface CreateSandboxParams {
8
9
  * The source of the sandbox.
9
10
  *
10
11
  * Omit this parameter start a sandbox without a source.
12
+ *
13
+ * For git sources:
14
+ * - `depth`: Creates shallow clones with limited commit history (minimum: 1)
15
+ * - `revision`: Clones and checks out a specific commit, branch, or tag
11
16
  */
12
17
  source?: {
13
18
  type: "git";
14
19
  url: string;
20
+ depth?: number;
21
+ revision?: string;
15
22
  } | {
16
23
  type: "tarball";
17
24
  url: string;
@@ -33,6 +40,11 @@ export interface CreateSandboxParams {
33
40
  resources?: {
34
41
  vcpus: number;
35
42
  };
43
+ /**
44
+ * The runtime of the sandbox, currently only `node22` and `python3.13` are supported.
45
+ * If not specified, the default runtime `node22` will be used.
46
+ */
47
+ runtime?: "node22" | "python3.13";
36
48
  }
37
49
  /** @inline */
38
50
  interface GetSandboxParams {
@@ -91,14 +103,15 @@ export declare class Sandbox {
91
103
  * Routes from ports to subdomains.
92
104
  /* @hidden
93
105
  */
94
- readonly routes: {
95
- subdomain: string;
96
- port: number;
97
- }[];
106
+ readonly routes: SandboxRouteData[];
98
107
  /**
99
108
  * Unique ID of this sandbox.
100
109
  */
101
- readonly sandboxId: string;
110
+ get sandboxId(): string;
111
+ /**
112
+ * Data about this sandbox.
113
+ */
114
+ private readonly sandbox;
102
115
  /**
103
116
  * Create a new sandbox.
104
117
  *
@@ -120,13 +133,10 @@ export declare class Sandbox {
120
133
  * @param routes - Port-to-subdomain mappings for exposed ports
121
134
  * @param sandboxId - Unique identifier for the sandbox
122
135
  */
123
- constructor({ client, routes, sandboxId, }: {
136
+ constructor({ client, routes, sandbox, }: {
124
137
  client: APIClient;
125
- routes: {
126
- subdomain: string;
127
- port: number;
128
- }[];
129
- sandboxId: string;
138
+ routes: SandboxRouteData[];
139
+ sandbox: SandboxData;
130
140
  });
131
141
  /**
132
142
  * Get a previously run command by its ID.
@@ -134,7 +144,7 @@ export declare class Sandbox {
134
144
  * @param cmdId - ID of the command to retrieve
135
145
  * @returns A {@link Command} instance representing the command
136
146
  */
137
- getCommand(cmdId: string): Command;
147
+ getCommand(cmdId: string): Promise<Command>;
138
148
  /**
139
149
  * Start executing a command in this sandbox.
140
150
  *
@@ -181,7 +191,7 @@ export declare class Sandbox {
181
191
  */
182
192
  writeFiles(files: {
183
193
  path: string;
184
- stream: Readable | Buffer;
194
+ stream: Buffer;
185
195
  }[]): Promise<void>;
186
196
  /**
187
197
  * Get the public domain of a port of this sandbox.
package/dist/sandbox.js CHANGED
@@ -11,6 +11,12 @@ const get_credentials_1 = require("./utils/get-credentials");
11
11
  * @hideconstructor
12
12
  */
13
13
  class Sandbox {
14
+ /**
15
+ * Unique ID of this sandbox.
16
+ */
17
+ get sandboxId() {
18
+ return this.sandbox.id;
19
+ }
14
20
  /**
15
21
  * Create a new sandbox.
16
22
  *
@@ -29,10 +35,11 @@ class Sandbox {
29
35
  ports: params?.ports ?? [],
30
36
  timeout: params?.timeout,
31
37
  resources: params?.resources,
38
+ runtime: params?.runtime,
32
39
  });
33
40
  return new Sandbox({
34
41
  client,
35
- sandboxId: sandbox.json.sandboxId,
42
+ sandbox: sandbox.json.sandbox,
36
43
  routes: sandbox.json.routes,
37
44
  });
38
45
  }
@@ -48,10 +55,13 @@ class Sandbox {
48
55
  teamId: credentials.teamId,
49
56
  token: credentials.token,
50
57
  });
58
+ const sandbox = await client.getSandbox({
59
+ sandboxId: params.sandboxId,
60
+ });
51
61
  return new Sandbox({
52
62
  client,
53
- sandboxId: params.sandboxId,
54
- routes: params.routes,
63
+ sandbox: sandbox.json.sandbox,
64
+ routes: sandbox.json.routes,
55
65
  });
56
66
  }
57
67
  /**
@@ -61,10 +71,10 @@ class Sandbox {
61
71
  * @param routes - Port-to-subdomain mappings for exposed ports
62
72
  * @param sandboxId - Unique identifier for the sandbox
63
73
  */
64
- constructor({ client, routes, sandboxId, }) {
74
+ constructor({ client, routes, sandbox, }) {
65
75
  this.client = client;
66
76
  this.routes = routes;
67
- this.sandboxId = sandboxId;
77
+ this.sandbox = sandbox;
68
78
  }
69
79
  /**
70
80
  * Get a previously run command by its ID.
@@ -72,11 +82,15 @@ class Sandbox {
72
82
  * @param cmdId - ID of the command to retrieve
73
83
  * @returns A {@link Command} instance representing the command
74
84
  */
75
- getCommand(cmdId) {
85
+ async getCommand(cmdId) {
86
+ const command = await this.client.getCommand({
87
+ sandboxId: this.sandbox.id,
88
+ cmdId,
89
+ });
76
90
  return new command_1.Command({
77
91
  client: this.client,
78
- sandboxId: this.sandboxId,
79
- cmdId,
92
+ sandboxId: this.sandbox.id,
93
+ cmd: command.json.command,
80
94
  });
81
95
  }
82
96
  async runCommand(commandOrParams, args) {
@@ -93,7 +107,7 @@ class Sandbox {
93
107
  */
94
108
  async _runCommand(params) {
95
109
  const commandResponse = await this.client.runCommand({
96
- sandboxId: this.sandboxId,
110
+ sandboxId: this.sandbox.id,
97
111
  command: params.cmd,
98
112
  args: params.args ?? [],
99
113
  cwd: params.cwd,
@@ -101,8 +115,8 @@ class Sandbox {
101
115
  });
102
116
  const command = new command_1.Command({
103
117
  client: this.client,
104
- sandboxId: this.sandboxId,
105
- cmdId: commandResponse.json.cmdId,
118
+ sandboxId: this.sandbox.id,
119
+ cmd: commandResponse.json.command,
106
120
  });
107
121
  if (params.stdout || params.stderr) {
108
122
  (async () => {
@@ -125,7 +139,7 @@ class Sandbox {
125
139
  */
126
140
  async mkDir(path) {
127
141
  await this.client.mkDir({
128
- sandboxId: this.sandboxId,
142
+ sandboxId: this.sandbox.id,
129
143
  path: path,
130
144
  });
131
145
  }
@@ -137,7 +151,7 @@ class Sandbox {
137
151
  */
138
152
  async writeFiles(files) {
139
153
  return this.client.writeFiles({
140
- sandboxId: this.sandboxId,
154
+ sandboxId: this.sandbox.id,
141
155
  files: files,
142
156
  });
143
157
  }
@@ -164,7 +178,7 @@ class Sandbox {
164
178
  */
165
179
  async stop() {
166
180
  await this.client.stopSandbox({
167
- sandboxId: this.sandboxId,
181
+ sandboxId: this.sandbox.id,
168
182
  });
169
183
  }
170
184
  }
@@ -0,0 +1,13 @@
1
+ declare const linuxSignalMapping: {
2
+ readonly SIGHUP: 1;
3
+ readonly SIGINT: 2;
4
+ readonly SIGQUIT: 3;
5
+ readonly SIGKILL: 9;
6
+ readonly SIGTERM: 15;
7
+ readonly SIGCONT: 18;
8
+ readonly SIGSTOP: 19;
9
+ };
10
+ type CommonLinuxSignals = keyof typeof linuxSignalMapping;
11
+ export type Signal = CommonLinuxSignals | number;
12
+ export declare function resolveSignal(signal: Signal): number;
13
+ export {};
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveSignal = resolveSignal;
4
+ const linuxSignalMapping = {
5
+ SIGHUP: 1,
6
+ SIGINT: 2,
7
+ SIGQUIT: 3,
8
+ SIGKILL: 9,
9
+ SIGTERM: 15,
10
+ SIGCONT: 18,
11
+ SIGSTOP: 19,
12
+ };
13
+ function resolveSignal(signal) {
14
+ if (typeof signal === "number") {
15
+ return signal;
16
+ }
17
+ if (signal in linuxSignalMapping) {
18
+ return linuxSignalMapping[signal];
19
+ }
20
+ throw new Error(`Unknown signal name: ${String(signal)}`);
21
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.0.7";
1
+ export declare const VERSION = "0.0.9";
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.7";
5
+ exports.VERSION = "0.0.9";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/sandbox",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,6 +15,8 @@
15
15
  "lru-cache": "11.1.0",
16
16
  "ms": "2.1.3",
17
17
  "node-fetch": "2.6.11",
18
+ "tar-stream": "3.1.7",
19
+ "zlib": "1.0.5",
18
20
  "zod": "3.24.4"
19
21
  },
20
22
  "devDependencies": {
@@ -23,6 +25,7 @@
23
25
  "@types/ms": "2.1.0",
24
26
  "@types/node": "22.15.12",
25
27
  "@types/node-fetch": "2.6.12",
28
+ "@types/tar-stream": "3.1.4",
26
29
  "dotenv": "16.5.0",
27
30
  "typedoc": "0.28.5",
28
31
  "typescript": "5.8.3",
@@ -1,4 +1,3 @@
1
- import FormData from "form-data";
2
1
  import {
3
2
  BaseClient,
4
3
  parseOrThrow,
@@ -6,16 +5,15 @@ import {
6
5
  type RequestParams,
7
6
  } from "./base-client";
8
7
  import {
9
- Command,
10
- CommandFinished,
11
- CreatedCommand,
12
- CreatedSandbox,
8
+ SandboxAndRoutesResponse,
9
+ SandboxResponse,
10
+ CommandResponse,
11
+ CommandFinishedResponse,
12
+ EmptyResponse,
13
13
  LogLine,
14
- StoppedSandbox,
15
- WrittenFile,
16
14
  } from "./validators";
17
- import { Readable } from "stream";
18
15
  import { APIError } from "./api-error";
16
+ import { FileWriter } from "./file-writer";
19
17
  import { LRUCache } from "lru-cache";
20
18
  import { VERSION } from "../version";
21
19
  import { z } from "zod";
@@ -57,15 +55,25 @@ export class APIClient extends BaseClient {
57
55
  });
58
56
  }
59
57
 
58
+ async getSandbox(params: { sandboxId: string }) {
59
+ return parseOrThrow(
60
+ SandboxAndRoutesResponse,
61
+ await this.request(`/v1/sandboxes/${params.sandboxId}`),
62
+ );
63
+ }
64
+
60
65
  async createSandbox(params: {
61
66
  ports?: number[];
62
67
  projectId: string;
63
- source?: { type: "git"; url: string } | { type: "tarball"; url: string };
68
+ source?:
69
+ | { type: "git"; url: string; depth?: number; revision?: string }
70
+ | { type: "tarball"; url: string };
64
71
  timeout?: number;
65
72
  resources?: { vcpus: number };
73
+ runtime?: "node22" | "python3.13";
66
74
  }) {
67
75
  return parseOrThrow(
68
- CreatedSandbox,
76
+ SandboxAndRoutesResponse,
69
77
  await this.request("/v1/sandboxes", {
70
78
  method: "POST",
71
79
  body: JSON.stringify({
@@ -74,6 +82,7 @@ export class APIClient extends BaseClient {
74
82
  source: params.source,
75
83
  timeout: params.timeout,
76
84
  resources: params.resources,
85
+ runtime: params.runtime,
77
86
  }),
78
87
  }),
79
88
  );
@@ -87,7 +96,7 @@ export class APIClient extends BaseClient {
87
96
  env: Record<string, string>;
88
97
  }) {
89
98
  return parseOrThrow(
90
- CreatedCommand,
99
+ CommandResponse,
91
100
  await this.request(`/v1/sandboxes/${params.sandboxId}/cmd`, {
92
101
  method: "POST",
93
102
  body: JSON.stringify({
@@ -104,12 +113,12 @@ export class APIClient extends BaseClient {
104
113
  sandboxId: string;
105
114
  cmdId: string;
106
115
  wait: true;
107
- }): Promise<Parsed<z.infer<typeof CommandFinished>>>;
116
+ }): Promise<Parsed<z.infer<typeof CommandFinishedResponse>>>;
108
117
  async getCommand(params: {
109
118
  sandboxId: string;
110
119
  cmdId: string;
111
120
  wait?: boolean;
112
- }): Promise<Parsed<z.infer<typeof Command>>>;
121
+ }): Promise<Parsed<z.infer<typeof CommandResponse>>>;
113
122
  async getCommand(params: {
114
123
  sandboxId: string;
115
124
  cmdId: string;
@@ -117,14 +126,14 @@ export class APIClient extends BaseClient {
117
126
  }) {
118
127
  return params.wait
119
128
  ? parseOrThrow(
120
- CommandFinished,
129
+ CommandFinishedResponse,
121
130
  await this.request(
122
131
  `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`,
123
132
  { query: { wait: "true" } },
124
133
  ),
125
134
  )
126
135
  : parseOrThrow(
127
- Command,
136
+ CommandResponse,
128
137
  await this.request(
129
138
  `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`,
130
139
  ),
@@ -133,7 +142,7 @@ export class APIClient extends BaseClient {
133
142
 
134
143
  async mkDir(params: { sandboxId: string; path: string; cwd?: string }) {
135
144
  return parseOrThrow(
136
- WrittenFile,
145
+ EmptyResponse,
137
146
  await this.request(`/v1/sandboxes/${params.sandboxId}/fs/mkdir`, {
138
147
  method: "POST",
139
148
  body: JSON.stringify({ path: params.path, cwd: params.cwd }),
@@ -141,24 +150,32 @@ export class APIClient extends BaseClient {
141
150
  );
142
151
  }
143
152
 
153
+ getFileWriter(params: { sandboxId: string }) {
154
+ const writer = new FileWriter();
155
+ 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
+ }),
161
+ writer,
162
+ };
163
+ }
164
+
144
165
  async writeFiles(params: {
145
166
  sandboxId: string;
146
- files: { path: string; stream: Readable | Buffer }[];
167
+ files: { path: string; stream: Buffer }[];
147
168
  }) {
148
- const formData = new FormData();
169
+ const { writer, response } = this.getFileWriter({
170
+ sandboxId: params.sandboxId,
171
+ });
149
172
 
150
173
  for (const file of params.files) {
151
- formData.append(file.path, file.stream, file.path);
174
+ await writer.addFile({ name: file.path, content: file.stream });
152
175
  }
153
176
 
154
- await parseOrThrow(
155
- WrittenFile,
156
- await this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
157
- method: "POST",
158
- headers: { ...formData.getHeaders() },
159
- body: formData,
160
- }),
161
- );
177
+ await writer.end();
178
+ await parseOrThrow(EmptyResponse, await response);
162
179
  }
163
180
 
164
181
  async readFile(params: {
@@ -181,6 +198,23 @@ export class APIClient extends BaseClient {
181
198
  return response.body;
182
199
  }
183
200
 
201
+ async killCommand(params: {
202
+ sandboxId: string;
203
+ commandId: string;
204
+ signal: number;
205
+ }) {
206
+ return parseOrThrow(
207
+ CommandResponse,
208
+ await this.request(
209
+ `/v1/sandboxes/${params.sandboxId}/${params.commandId}/kill`,
210
+ {
211
+ method: "POST",
212
+ body: JSON.stringify({ signal: params.signal }),
213
+ },
214
+ ),
215
+ );
216
+ }
217
+
184
218
  async *getLogs(params: {
185
219
  sandboxId: string;
186
220
  cmdId: string;
@@ -215,10 +249,10 @@ export class APIClient extends BaseClient {
215
249
 
216
250
  async stopSandbox(params: {
217
251
  sandboxId: string;
218
- }): Promise<Parsed<z.infer<typeof StoppedSandbox>>> {
252
+ }): Promise<Parsed<z.infer<typeof SandboxResponse>>> {
219
253
  const url = `/v1/sandboxes/${params.sandboxId}/stop`;
220
254
  return parseOrThrow(
221
- StoppedSandbox,
255
+ SandboxResponse,
222
256
  await this.request(url, { method: "POST" }),
223
257
  );
224
258
  }
@@ -0,0 +1,90 @@
1
+ import zlib from "zlib";
2
+ import tar, { type Pack } from "tar-stream";
3
+ import { Readable } from "stream";
4
+
5
+ interface FileBuffer {
6
+ /**
7
+ * The name (path) of the file to write.
8
+ */
9
+ name: string;
10
+ /**
11
+ * The content of the file as a Buffer.
12
+ */
13
+ content: Buffer;
14
+ }
15
+
16
+ interface FileStream {
17
+ /**
18
+ * The name (path) of the file to write.
19
+ */
20
+ name: string;
21
+ /**
22
+ * A Readable stream to consume the content of the file.
23
+ */
24
+ content: Readable;
25
+ /**
26
+ * The expected size of the file. This is required to write
27
+ * the header of the compressed file.
28
+ */
29
+ size: number;
30
+ }
31
+
32
+ /**
33
+ * Allows to create a Readable stream with methods to write files
34
+ * to it and to finish it. Files written are compressed together
35
+ * and gzipped in the stream.
36
+ */
37
+ export class FileWriter {
38
+ public readable: Readable;
39
+ private pack: Pack;
40
+
41
+ constructor() {
42
+ const gzip = zlib.createGzip();
43
+ this.pack = tar.pack();
44
+ this.readable = this.pack.pipe(gzip);
45
+ }
46
+
47
+ /**
48
+ * Allows to add a file to the stream. Size is required to write
49
+ * the tarball header so when content is a stream it must be
50
+ * provided.
51
+ *
52
+ * Returns a Promise resolved once the file is written in the
53
+ * stream.
54
+ */
55
+ async addFile(file: FileBuffer | FileStream) {
56
+ return new Promise<void>((resolve, reject) => {
57
+ const entry = this.pack.entry(
58
+ "size" in file
59
+ ? { name: file.name, size: file.size }
60
+ : { name: file.name, size: file.content.length },
61
+ (error) => {
62
+ if (error) {
63
+ return reject(error);
64
+ } else {
65
+ resolve();
66
+ }
67
+ },
68
+ );
69
+
70
+ if (file.content instanceof Readable) {
71
+ file.content.pipe(entry);
72
+ } else {
73
+ entry.end(file.content);
74
+ }
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Allows to finish the stream returning a Promise that will
80
+ * resolve once the readable is effectively closed or
81
+ * errored.
82
+ */
83
+ async end() {
84
+ return new Promise<void>((resolve, reject) => {
85
+ this.readable.on("error", reject);
86
+ this.readable.on("end", resolve);
87
+ this.pack.finalize();
88
+ });
89
+ }
90
+ }
@@ -1 +1,2 @@
1
1
  export { APIClient } from "./api-client";
2
+ export * from "./validators";
@@ -1,36 +1,66 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const CreatedSandbox = z.object({
4
- sandboxId: z.string(),
5
- routes: z.array(z.object({ subdomain: z.string(), port: z.number() })),
3
+ export type SandboxData = z.infer<typeof Sandbox>;
4
+
5
+ export const Sandbox = z.object({
6
+ id: z.string(),
7
+ memory: z.number(),
8
+ vcpus: z.number(),
9
+ region: z.string(),
10
+ runtime: z.string(),
11
+ timeout: z.number(),
12
+ status: z.enum(["pending", "running", "stopping", "stopped", "failed"]),
13
+ requestedAt: z.number(),
14
+ startedAt: z.number().optional(),
15
+ requestedStopAt: z.number().optional(),
16
+ stoppedAt: z.number().optional(),
17
+ duration: z.number().optional(),
18
+ createdAt: z.number(),
19
+ updatedAt: z.number(),
6
20
  });
7
21
 
8
- export const CreatedCommand = z.object({
9
- cmdId: z.string(),
22
+ export type SandboxRouteData = z.infer<typeof SandboxRoute>;
23
+
24
+ export const SandboxRoute = z.object({
25
+ url: z.string(),
26
+ subdomain: z.string(),
27
+ port: z.number(),
10
28
  });
11
29
 
30
+ export type CommandData = z.infer<typeof Command>;
31
+
12
32
  export const Command = z.object({
33
+ id: z.string(),
34
+ name: z.string(),
13
35
  args: z.array(z.string()),
14
- cmdId: z.string(),
15
36
  cwd: z.string(),
37
+ sandboxId: z.string(),
16
38
  exitCode: z.number().nullable(),
17
- name: z.string(),
39
+ startedAt: z.number(),
18
40
  });
19
41
 
20
- export const CommandFinished = z.object({
21
- args: z.array(z.string()),
22
- cmdId: z.string(),
23
- cwd: z.string(),
42
+ const CommandFinished = Command.extend({
24
43
  exitCode: z.number(),
25
- name: z.string(),
26
44
  });
27
45
 
28
- export const WrittenFile = z.object({});
46
+ export const SandboxResponse = z.object({
47
+ sandbox: Sandbox,
48
+ });
29
49
 
30
- export const StoppedSandbox = z.object({
31
- sandboxId: z.string(),
50
+ export const SandboxAndRoutesResponse = SandboxResponse.extend({
51
+ routes: z.array(SandboxRoute),
52
+ });
53
+
54
+ export const CommandResponse = z.object({
55
+ command: Command,
56
+ });
57
+
58
+ export const CommandFinishedResponse = z.object({
59
+ command: CommandFinished,
32
60
  });
33
61
 
62
+ export const EmptyResponse = z.object({});
63
+
34
64
  export const LogLine = z.object({
35
65
  stream: z.enum(["stdout", "stderr"]),
36
66
  data: z.string(),