@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/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 });
@@ -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
@@ -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 {
@@ -15,7 +16,20 @@ export interface CreateSandboxParams {
15
16
  * - `revision`: Clones and checks out a specific commit, branch, or tag
16
17
  */
17
18
  source?:
18
- | { type: "git"; url: string; depth?: number; revision?: string }
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
+ }
19
33
  | { type: "tarball"; url: string };
20
34
  /**
21
35
  * Array of port numbers to expose from the sandbox.
@@ -42,10 +56,6 @@ export interface CreateSandboxParams {
42
56
 
43
57
  /** @inline */
44
58
  interface GetSandboxParams {
45
- /**
46
- * Port-to-subdomain route mappings.
47
- */
48
- routes: Array<{ subdomain: string; port: number }>;
49
59
  /**
50
60
  * Unique identifier of the sandbox.
51
61
  */
@@ -97,12 +107,19 @@ export class Sandbox {
97
107
  * Routes from ports to subdomains.
98
108
  /* @hidden
99
109
  */
100
- public readonly routes: { subdomain: string; port: number }[];
110
+ public readonly routes: SandboxRouteData[];
101
111
 
102
112
  /**
103
113
  * Unique ID of this sandbox.
104
114
  */
105
- public readonly sandboxId: string;
115
+ public get sandboxId(): string {
116
+ return this.sandbox.id;
117
+ }
118
+
119
+ /**
120
+ * Data about this sandbox.
121
+ */
122
+ private readonly sandbox: SandboxData;
106
123
 
107
124
  /**
108
125
  * Create a new sandbox.
@@ -130,7 +147,7 @@ export class Sandbox {
130
147
 
131
148
  return new Sandbox({
132
149
  client,
133
- sandboxId: sandbox.json.sandboxId,
150
+ sandbox: sandbox.json.sandbox,
134
151
  routes: sandbox.json.routes,
135
152
  });
136
153
  }
@@ -150,10 +167,14 @@ export class Sandbox {
150
167
  token: credentials.token,
151
168
  });
152
169
 
170
+ const sandbox = await client.getSandbox({
171
+ sandboxId: params.sandboxId,
172
+ });
173
+
153
174
  return new Sandbox({
154
175
  client,
155
- sandboxId: params.sandboxId,
156
- routes: params.routes,
176
+ sandbox: sandbox.json.sandbox,
177
+ routes: sandbox.json.routes,
157
178
  });
158
179
  }
159
180
 
@@ -167,15 +188,15 @@ export class Sandbox {
167
188
  constructor({
168
189
  client,
169
190
  routes,
170
- sandboxId,
191
+ sandbox,
171
192
  }: {
172
193
  client: APIClient;
173
- routes: { subdomain: string; port: number }[];
174
- sandboxId: string;
194
+ routes: SandboxRouteData[];
195
+ sandbox: SandboxData;
175
196
  }) {
176
197
  this.client = client;
177
198
  this.routes = routes;
178
- this.sandboxId = sandboxId;
199
+ this.sandbox = sandbox;
179
200
  }
180
201
 
181
202
  /**
@@ -184,11 +205,16 @@ export class Sandbox {
184
205
  * @param cmdId - ID of the command to retrieve
185
206
  * @returns A {@link Command} instance representing the command
186
207
  */
187
- getCommand(cmdId: string): Command {
208
+ async getCommand(cmdId: string): Promise<Command> {
209
+ const command = await this.client.getCommand({
210
+ sandboxId: this.sandbox.id,
211
+ cmdId,
212
+ });
213
+
188
214
  return new Command({
189
215
  client: this.client,
190
- sandboxId: this.sandboxId,
191
- cmdId,
216
+ sandboxId: this.sandbox.id,
217
+ cmd: command.json.command,
192
218
  });
193
219
  }
194
220
 
@@ -237,7 +263,7 @@ export class Sandbox {
237
263
  */
238
264
  async _runCommand(params: RunCommandParams) {
239
265
  const commandResponse = await this.client.runCommand({
240
- sandboxId: this.sandboxId,
266
+ sandboxId: this.sandbox.id,
241
267
  command: params.cmd,
242
268
  args: params.args ?? [],
243
269
  cwd: params.cwd,
@@ -246,8 +272,8 @@ export class Sandbox {
246
272
 
247
273
  const command = new Command({
248
274
  client: this.client,
249
- sandboxId: this.sandboxId,
250
- cmdId: commandResponse.json.cmdId,
275
+ sandboxId: this.sandbox.id,
276
+ cmd: commandResponse.json.command,
251
277
  });
252
278
 
253
279
  if (params.stdout || params.stderr) {
@@ -272,11 +298,25 @@ export class Sandbox {
272
298
  */
273
299
  async mkDir(path: string): Promise<void> {
274
300
  await this.client.mkDir({
275
- sandboxId: this.sandboxId,
301
+ sandboxId: this.sandbox.id,
276
302
  path: path,
277
303
  });
278
304
  }
279
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
+
280
320
  /**
281
321
  * Write files to the filesystem of this sandbox.
282
322
  *
@@ -285,7 +325,7 @@ export class Sandbox {
285
325
  */
286
326
  async writeFiles(files: { path: string; stream: Buffer }[]) {
287
327
  return this.client.writeFiles({
288
- sandboxId: this.sandboxId,
328
+ sandboxId: this.sandbox.id,
289
329
  files: files,
290
330
  });
291
331
  }
@@ -313,7 +353,7 @@ export class Sandbox {
313
353
  */
314
354
  async stop() {
315
355
  await this.client.stopSandbox({
316
- sandboxId: this.sandboxId,
356
+ sandboxId: this.sandbox.id,
317
357
  });
318
358
  }
319
359
  }
@@ -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
+ }
@@ -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.10";