@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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +61 -12
- package/dist/api-client/api-client.d.ts +72 -6
- package/dist/api-client/api-client.js +25 -13
- package/dist/api-client/index.d.ts +1 -0
- package/dist/api-client/index.js +15 -0
- package/dist/api-client/validators.d.ts +343 -50
- package/dist/api-client/validators.js +37 -16
- package/dist/command.d.ts +18 -5
- package/dist/command.js +27 -6
- package/dist/sandbox.d.ts +30 -21
- package/dist/sandbox.js +40 -14
- package/dist/utils/consume-readable.d.ts +5 -0
- package/dist/utils/consume-readable.js +15 -0
- package/dist/utils/resolveSignal.d.ts +13 -0
- package/dist/utils/resolveSignal.js +21 -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 +56 -23
- package/src/api-client/index.ts +1 -0
- package/src/api-client/validators.ts +45 -15
- package/src/command.test.ts +39 -16
- package/src/command.ts +33 -10
- package/src/sandbox.test.ts +29 -0
- package/src/sandbox.ts +66 -26
- package/src/utils/consume-readable.ts +12 -0
- package/src/utils/resolveSignal.ts +24 -0
- package/src/version.ts +1 -1
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
|
-
|
|
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
|
-
|
|
47
|
+
cmd,
|
|
40
48
|
}: {
|
|
41
49
|
client: APIClient;
|
|
42
50
|
sandboxId: string;
|
|
43
|
-
|
|
51
|
+
cmd: CommandData;
|
|
44
52
|
}) {
|
|
45
53
|
this.client = client;
|
|
46
54
|
this.sandboxId = sandboxId;
|
|
47
|
-
this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
| {
|
|
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:
|
|
110
|
+
public readonly routes: SandboxRouteData[];
|
|
101
111
|
|
|
102
112
|
/**
|
|
103
113
|
* Unique ID of this sandbox.
|
|
104
114
|
*/
|
|
105
|
-
public
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
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
|
-
|
|
191
|
+
sandbox,
|
|
171
192
|
}: {
|
|
172
193
|
client: APIClient;
|
|
173
|
-
routes:
|
|
174
|
-
|
|
194
|
+
routes: SandboxRouteData[];
|
|
195
|
+
sandbox: SandboxData;
|
|
175
196
|
}) {
|
|
176
197
|
this.client = client;
|
|
177
198
|
this.routes = routes;
|
|
178
|
-
this.
|
|
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.
|
|
191
|
-
|
|
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.
|
|
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.
|
|
250
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
2
|
+
export const VERSION = "0.0.10";
|