@vercel/sandbox 0.0.8 → 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 { 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 {
@@ -97,12 +98,19 @@ export class Sandbox {
97
98
  * Routes from ports to subdomains.
98
99
  /* @hidden
99
100
  */
100
- public readonly routes: { subdomain: string; port: number }[];
101
+ public readonly routes: SandboxRouteData[];
101
102
 
102
103
  /**
103
104
  * Unique ID of this sandbox.
104
105
  */
105
- 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;
106
114
 
107
115
  /**
108
116
  * Create a new sandbox.
@@ -130,7 +138,7 @@ export class Sandbox {
130
138
 
131
139
  return new Sandbox({
132
140
  client,
133
- sandboxId: sandbox.json.sandboxId,
141
+ sandbox: sandbox.json.sandbox,
134
142
  routes: sandbox.json.routes,
135
143
  });
136
144
  }
@@ -150,10 +158,14 @@ export class Sandbox {
150
158
  token: credentials.token,
151
159
  });
152
160
 
161
+ const sandbox = await client.getSandbox({
162
+ sandboxId: params.sandboxId,
163
+ });
164
+
153
165
  return new Sandbox({
154
166
  client,
155
- sandboxId: params.sandboxId,
156
- routes: params.routes,
167
+ sandbox: sandbox.json.sandbox,
168
+ routes: sandbox.json.routes,
157
169
  });
158
170
  }
159
171
 
@@ -167,15 +179,15 @@ export class Sandbox {
167
179
  constructor({
168
180
  client,
169
181
  routes,
170
- sandboxId,
182
+ sandbox,
171
183
  }: {
172
184
  client: APIClient;
173
- routes: { subdomain: string; port: number }[];
174
- sandboxId: string;
185
+ routes: SandboxRouteData[];
186
+ sandbox: SandboxData;
175
187
  }) {
176
188
  this.client = client;
177
189
  this.routes = routes;
178
- this.sandboxId = sandboxId;
190
+ this.sandbox = sandbox;
179
191
  }
180
192
 
181
193
  /**
@@ -184,11 +196,16 @@ export class Sandbox {
184
196
  * @param cmdId - ID of the command to retrieve
185
197
  * @returns A {@link Command} instance representing the command
186
198
  */
187
- 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
+
188
205
  return new Command({
189
206
  client: this.client,
190
- sandboxId: this.sandboxId,
191
- cmdId,
207
+ sandboxId: this.sandbox.id,
208
+ cmd: command.json.command,
192
209
  });
193
210
  }
194
211
 
@@ -237,7 +254,7 @@ export class Sandbox {
237
254
  */
238
255
  async _runCommand(params: RunCommandParams) {
239
256
  const commandResponse = await this.client.runCommand({
240
- sandboxId: this.sandboxId,
257
+ sandboxId: this.sandbox.id,
241
258
  command: params.cmd,
242
259
  args: params.args ?? [],
243
260
  cwd: params.cwd,
@@ -246,8 +263,8 @@ export class Sandbox {
246
263
 
247
264
  const command = new Command({
248
265
  client: this.client,
249
- sandboxId: this.sandboxId,
250
- cmdId: commandResponse.json.cmdId,
266
+ sandboxId: this.sandbox.id,
267
+ cmd: commandResponse.json.command,
251
268
  });
252
269
 
253
270
  if (params.stdout || params.stderr) {
@@ -272,7 +289,7 @@ export class Sandbox {
272
289
  */
273
290
  async mkDir(path: string): Promise<void> {
274
291
  await this.client.mkDir({
275
- sandboxId: this.sandboxId,
292
+ sandboxId: this.sandbox.id,
276
293
  path: path,
277
294
  });
278
295
  }
@@ -285,7 +302,7 @@ export class Sandbox {
285
302
  */
286
303
  async writeFiles(files: { path: string; stream: Buffer }[]) {
287
304
  return this.client.writeFiles({
288
- sandboxId: this.sandboxId,
305
+ sandboxId: this.sandbox.id,
289
306
  files: files,
290
307
  });
291
308
  }
@@ -313,7 +330,7 @@ export class Sandbox {
313
330
  */
314
331
  async stop() {
315
332
  await this.client.stopSandbox({
316
- sandboxId: this.sandboxId,
333
+ sandboxId: this.sandbox.id,
317
334
  });
318
335
  }
319
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.8";
2
+ export const VERSION = "0.0.9";