@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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +7 -0
- package/dist/api-client/api-client.d.ts +70 -6
- package/dist/api-client/api-client.js +16 -7
- 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 +13 -14
- package/dist/sandbox.js +27 -14
- 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 +39 -16
- 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.ts +38 -21
- package/src/utils/resolveSignal.ts +24 -0
- package/src/version.ts +1 -1
package/src/command.test.ts
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { Sandbox } from "./sandbox";
|
|
3
3
|
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 });
|
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 {
|
|
@@ -97,12 +98,19 @@ export class Sandbox {
|
|
|
97
98
|
* Routes from ports to subdomains.
|
|
98
99
|
/* @hidden
|
|
99
100
|
*/
|
|
100
|
-
public readonly routes:
|
|
101
|
+
public readonly routes: SandboxRouteData[];
|
|
101
102
|
|
|
102
103
|
/**
|
|
103
104
|
* Unique ID of this sandbox.
|
|
104
105
|
*/
|
|
105
|
-
public
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
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
|
-
|
|
182
|
+
sandbox,
|
|
171
183
|
}: {
|
|
172
184
|
client: APIClient;
|
|
173
|
-
routes:
|
|
174
|
-
|
|
185
|
+
routes: SandboxRouteData[];
|
|
186
|
+
sandbox: SandboxData;
|
|
175
187
|
}) {
|
|
176
188
|
this.client = client;
|
|
177
189
|
this.routes = routes;
|
|
178
|
-
this.
|
|
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.
|
|
191
|
-
|
|
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.
|
|
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.
|
|
250
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
2
|
+
export const VERSION = "0.0.9";
|