@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.
@@ -1,17 +1,25 @@
1
- import { it, expect, vi } from "vitest";
1
+ import { expect, it, vi, beforeEach, afterEach } from "vitest";
2
2
  import { Sandbox } from "./sandbox";
3
3
 
4
- it("warns when there is more than one logs consumer", async () => {
5
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
6
- const stdoutSpy = vi
7
- .spyOn(process.stdout, "write")
8
- .mockImplementation(() => true);
4
+ let sandbox: Sandbox;
9
5
 
10
- const sandbox = await Sandbox.create({
6
+ beforeEach(async () => {
7
+ sandbox = await Sandbox.create({
11
8
  projectId: process.env.VERCEL_PROJECT_ID!,
12
9
  teamId: process.env.VERCEL_TEAM_ID!,
13
10
  token: process.env.VERCEL_TOKEN!,
14
11
  });
12
+ });
13
+
14
+ afterEach(async () => {
15
+ await sandbox.stop();
16
+ });
17
+
18
+ it("warns when there is more than one logs consumer", async () => {
19
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
20
+ const stdoutSpy = vi
21
+ .spyOn(process.stdout, "write")
22
+ .mockImplementation(() => true);
15
23
 
16
24
  const cmd = await sandbox.runCommand({
17
25
  cmd: "echo",
@@ -26,19 +34,11 @@ it("warns when there is more than one logs consumer", async () => {
26
34
  /Multiple consumers for logs of command `[^`]+`\.\sThis may lead to unexpected behavior\./,
27
35
  ),
28
36
  );
29
-
30
- await sandbox.stop();
31
37
  });
32
38
 
33
39
  it("does not warn when there is only one logs consumer", async () => {
34
40
  const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
35
41
 
36
- const sandbox = await Sandbox.create({
37
- projectId: process.env.VERCEL_PROJECT_ID!,
38
- teamId: process.env.VERCEL_TEAM_ID!,
39
- token: process.env.VERCEL_TOKEN!,
40
- });
41
-
42
42
  const cmd = await sandbox.runCommand({
43
43
  cmd: "echo",
44
44
  args: ["Hello World!"],
@@ -46,6 +46,29 @@ it("does not warn when there is only one logs consumer", async () => {
46
46
 
47
47
  expect(await cmd.stdout()).toEqual("Hello World!\n");
48
48
  expect(warnSpy).not.toHaveBeenCalled();
49
+ });
49
50
 
50
- await sandbox.stop();
51
+ it("Kills a command with a SIGINT", async () => {
52
+ const cmd = await sandbox.runCommand({
53
+ cmd: "sleep",
54
+ args: ["200000"],
55
+ detached: true,
56
+ });
57
+
58
+ await cmd.kill("SIGINT");
59
+ const result = await cmd.wait();
60
+ expect(result.exitCode).toBe(130); // 128 + 2
61
+ });
62
+
63
+ it("Kills a command with a SIGTERM", async () => {
64
+ const cmd = await sandbox.runCommand({
65
+ cmd: "sleep",
66
+ args: ["200000"],
67
+ detached: true,
68
+ });
69
+
70
+ await cmd.kill("SIGTERM");
71
+
72
+ const result = await cmd.wait();
73
+ expect(result.exitCode).toBe(143); // 128 + 15
51
74
  });
package/src/command.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { APIClient } from "./api-client";
1
+ import { APIClient, type CommandData } from "./api-client";
2
+ import { Signal, resolveSignal } from "./utils/resolveSignal";
2
3
 
3
4
  /**
4
5
  * A command executed in a Sandbox.
@@ -22,10 +23,17 @@ export class Command {
22
23
  */
23
24
  private sandboxId: string;
24
25
 
26
+ /**
27
+ * Data for the command execution.
28
+ */
29
+ private cmd: CommandData;
30
+
25
31
  /**
26
32
  * ID of the command execution.
27
33
  */
28
- public cmdId: string;
34
+ get cmdId() {
35
+ return this.cmd.id;
36
+ }
29
37
 
30
38
  /**
31
39
  * @param params - Object containing the client, sandbox ID, and command ID.
@@ -36,15 +44,15 @@ export class Command {
36
44
  constructor({
37
45
  client,
38
46
  sandboxId,
39
- cmdId,
47
+ cmd,
40
48
  }: {
41
49
  client: APIClient;
42
50
  sandboxId: string;
43
- cmdId: string;
51
+ cmd: CommandData;
44
52
  }) {
45
53
  this.client = client;
46
54
  this.sandboxId = sandboxId;
47
- this.cmdId = cmdId;
55
+ this.cmd = cmd;
48
56
  }
49
57
 
50
58
  /**
@@ -68,7 +76,7 @@ export class Command {
68
76
  logs() {
69
77
  return this.client.getLogs({
70
78
  sandboxId: this.sandboxId,
71
- cmdId: this.cmdId,
79
+ cmdId: this.cmd.id,
72
80
  });
73
81
  }
74
82
 
@@ -87,15 +95,15 @@ export class Command {
87
95
  async wait() {
88
96
  const command = await this.client.getCommand({
89
97
  sandboxId: this.sandboxId,
90
- cmdId: this.cmdId,
98
+ cmdId: this.cmd.id,
91
99
  wait: true,
92
100
  });
93
101
 
94
102
  return new CommandFinished({
95
103
  client: this.client,
96
104
  sandboxId: this.sandboxId,
97
- cmdId: command.json.cmdId,
98
- exitCode: command.json.exitCode,
105
+ cmd: command.json.command,
106
+ exitCode: command.json.command.exitCode,
99
107
  });
100
108
  }
101
109
 
@@ -141,6 +149,21 @@ export class Command {
141
149
  async stderr() {
142
150
  return this.output("stderr");
143
151
  }
152
+
153
+ /**
154
+ * Kill a running command in a sandbox.
155
+ *
156
+ * @param params - commandId and the signal to send the running process.
157
+ * Defaults to SIGTERM.
158
+ * @returns Promise<void>.
159
+ */
160
+ async kill(signal?: Signal) {
161
+ await this.client.killCommand({
162
+ sandboxId: this.sandboxId,
163
+ commandId: this.cmd.id,
164
+ signal: resolveSignal(signal ?? "SIGTERM"),
165
+ });
166
+ }
144
167
  }
145
168
 
146
169
  /**
@@ -167,7 +190,7 @@ export class CommandFinished extends Command {
167
190
  constructor(params: {
168
191
  client: APIClient;
169
192
  sandboxId: string;
170
- cmdId: string;
193
+ cmd: CommandData;
171
194
  exitCode: number;
172
195
  }) {
173
196
  super({ ...params });
package/src/sandbox.ts CHANGED
@@ -1,7 +1,8 @@
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 { getCredentials, type Credentials } from "./utils/get-credentials";
4
+ import { Command, type CommandFinished } from "./command";
5
+ import { type Credentials, getCredentials } from "./utils/get-credentials";
5
6
 
6
7
  /** @inline */
7
8
  export interface CreateSandboxParams {
@@ -9,8 +10,14 @@ export interface CreateSandboxParams {
9
10
  * The source of the sandbox.
10
11
  *
11
12
  * Omit this parameter start a sandbox without a source.
13
+ *
14
+ * For git sources:
15
+ * - `depth`: Creates shallow clones with limited commit history (minimum: 1)
16
+ * - `revision`: Clones and checks out a specific commit, branch, or tag
12
17
  */
13
- source?: { type: "git"; url: string } | { type: "tarball"; url: string };
18
+ source?:
19
+ | { type: "git"; url: string; depth?: number; revision?: string }
20
+ | { type: "tarball"; url: string };
14
21
  /**
15
22
  * Array of port numbers to expose from the sandbox.
16
23
  */
@@ -26,6 +33,12 @@ export interface CreateSandboxParams {
26
33
  * 2048 MB of memory per vCPU.
27
34
  */
28
35
  resources?: { vcpus: number };
36
+
37
+ /**
38
+ * The runtime of the sandbox, currently only `node22` and `python3.13` are supported.
39
+ * If not specified, the default runtime `node22` will be used.
40
+ */
41
+ runtime?: "node22" | "python3.13";
29
42
  }
30
43
 
31
44
  /** @inline */
@@ -85,12 +98,19 @@ export class Sandbox {
85
98
  * Routes from ports to subdomains.
86
99
  /* @hidden
87
100
  */
88
- public readonly routes: { subdomain: string; port: number }[];
101
+ public readonly routes: SandboxRouteData[];
89
102
 
90
103
  /**
91
104
  * Unique ID of this sandbox.
92
105
  */
93
- public readonly sandboxId: string;
106
+ public get sandboxId(): string {
107
+ return this.sandbox.id;
108
+ }
109
+
110
+ /**
111
+ * Data about this sandbox.
112
+ */
113
+ private readonly sandbox: SandboxData;
94
114
 
95
115
  /**
96
116
  * Create a new sandbox.
@@ -113,11 +133,12 @@ export class Sandbox {
113
133
  ports: params?.ports ?? [],
114
134
  timeout: params?.timeout,
115
135
  resources: params?.resources,
136
+ runtime: params?.runtime,
116
137
  });
117
138
 
118
139
  return new Sandbox({
119
140
  client,
120
- sandboxId: sandbox.json.sandboxId,
141
+ sandbox: sandbox.json.sandbox,
121
142
  routes: sandbox.json.routes,
122
143
  });
123
144
  }
@@ -137,10 +158,14 @@ export class Sandbox {
137
158
  token: credentials.token,
138
159
  });
139
160
 
161
+ const sandbox = await client.getSandbox({
162
+ sandboxId: params.sandboxId,
163
+ });
164
+
140
165
  return new Sandbox({
141
166
  client,
142
- sandboxId: params.sandboxId,
143
- routes: params.routes,
167
+ sandbox: sandbox.json.sandbox,
168
+ routes: sandbox.json.routes,
144
169
  });
145
170
  }
146
171
 
@@ -154,15 +179,15 @@ export class Sandbox {
154
179
  constructor({
155
180
  client,
156
181
  routes,
157
- sandboxId,
182
+ sandbox,
158
183
  }: {
159
184
  client: APIClient;
160
- routes: { subdomain: string; port: number }[];
161
- sandboxId: string;
185
+ routes: SandboxRouteData[];
186
+ sandbox: SandboxData;
162
187
  }) {
163
188
  this.client = client;
164
189
  this.routes = routes;
165
- this.sandboxId = sandboxId;
190
+ this.sandbox = sandbox;
166
191
  }
167
192
 
168
193
  /**
@@ -171,11 +196,16 @@ export class Sandbox {
171
196
  * @param cmdId - ID of the command to retrieve
172
197
  * @returns A {@link Command} instance representing the command
173
198
  */
174
- getCommand(cmdId: string): Command {
199
+ async getCommand(cmdId: string): Promise<Command> {
200
+ const command = await this.client.getCommand({
201
+ sandboxId: this.sandbox.id,
202
+ cmdId,
203
+ });
204
+
175
205
  return new Command({
176
206
  client: this.client,
177
- sandboxId: this.sandboxId,
178
- cmdId,
207
+ sandboxId: this.sandbox.id,
208
+ cmd: command.json.command,
179
209
  });
180
210
  }
181
211
 
@@ -224,7 +254,7 @@ export class Sandbox {
224
254
  */
225
255
  async _runCommand(params: RunCommandParams) {
226
256
  const commandResponse = await this.client.runCommand({
227
- sandboxId: this.sandboxId,
257
+ sandboxId: this.sandbox.id,
228
258
  command: params.cmd,
229
259
  args: params.args ?? [],
230
260
  cwd: params.cwd,
@@ -233,8 +263,8 @@ export class Sandbox {
233
263
 
234
264
  const command = new Command({
235
265
  client: this.client,
236
- sandboxId: this.sandboxId,
237
- cmdId: commandResponse.json.cmdId,
266
+ sandboxId: this.sandbox.id,
267
+ cmd: commandResponse.json.command,
238
268
  });
239
269
 
240
270
  if (params.stdout || params.stderr) {
@@ -259,7 +289,7 @@ export class Sandbox {
259
289
  */
260
290
  async mkDir(path: string): Promise<void> {
261
291
  await this.client.mkDir({
262
- sandboxId: this.sandboxId,
292
+ sandboxId: this.sandbox.id,
263
293
  path: path,
264
294
  });
265
295
  }
@@ -270,9 +300,9 @@ export class Sandbox {
270
300
  * @param files - Array of files with path and stream/buffer contents
271
301
  * @returns A promise that resolves when the files are written
272
302
  */
273
- async writeFiles(files: { path: string; stream: Readable | Buffer }[]) {
303
+ async writeFiles(files: { path: string; stream: Buffer }[]) {
274
304
  return this.client.writeFiles({
275
- sandboxId: this.sandboxId,
305
+ sandboxId: this.sandbox.id,
276
306
  files: files,
277
307
  });
278
308
  }
@@ -300,7 +330,7 @@ export class Sandbox {
300
330
  */
301
331
  async stop() {
302
332
  await this.client.stopSandbox({
303
- sandboxId: this.sandboxId,
333
+ sandboxId: this.sandbox.id,
304
334
  });
305
335
  }
306
336
  }
@@ -0,0 +1,24 @@
1
+ const linuxSignalMapping = {
2
+ SIGHUP: 1,
3
+ SIGINT: 2,
4
+ SIGQUIT: 3,
5
+ SIGKILL: 9,
6
+ SIGTERM: 15,
7
+ SIGCONT: 18,
8
+ SIGSTOP: 19,
9
+ } as const;
10
+
11
+ type CommonLinuxSignals = keyof typeof linuxSignalMapping;
12
+
13
+ export type Signal = CommonLinuxSignals | number;
14
+
15
+ export function resolveSignal(signal: Signal): number {
16
+ if (typeof signal === "number") {
17
+ return signal;
18
+ }
19
+
20
+ if (signal in linuxSignalMapping) {
21
+ return linuxSignalMapping[signal];
22
+ }
23
+ throw new Error(`Unknown signal name: ${String(signal)}`);
24
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Autogenerated by inject-version.ts
2
- export const VERSION = "0.0.7";
2
+ export const VERSION = "0.0.9";