@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/dist/sandbox.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 { Command, type CommandFinished } from "./command";
|
|
4
5
|
import { type Credentials } from "./utils/get-credentials";
|
|
5
6
|
/** @inline */
|
|
6
7
|
export interface CreateSandboxParams {
|
|
@@ -18,6 +19,13 @@ export interface CreateSandboxParams {
|
|
|
18
19
|
url: string;
|
|
19
20
|
depth?: number;
|
|
20
21
|
revision?: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: "git";
|
|
24
|
+
url: string;
|
|
25
|
+
username: string;
|
|
26
|
+
password: string;
|
|
27
|
+
depth?: number;
|
|
28
|
+
revision?: string;
|
|
21
29
|
} | {
|
|
22
30
|
type: "tarball";
|
|
23
31
|
url: string;
|
|
@@ -47,13 +55,6 @@ export interface CreateSandboxParams {
|
|
|
47
55
|
}
|
|
48
56
|
/** @inline */
|
|
49
57
|
interface GetSandboxParams {
|
|
50
|
-
/**
|
|
51
|
-
* Port-to-subdomain route mappings.
|
|
52
|
-
*/
|
|
53
|
-
routes: Array<{
|
|
54
|
-
subdomain: string;
|
|
55
|
-
port: number;
|
|
56
|
-
}>;
|
|
57
58
|
/**
|
|
58
59
|
* Unique identifier of the sandbox.
|
|
59
60
|
*/
|
|
@@ -102,14 +103,15 @@ export declare class Sandbox {
|
|
|
102
103
|
* Routes from ports to subdomains.
|
|
103
104
|
/* @hidden
|
|
104
105
|
*/
|
|
105
|
-
readonly routes:
|
|
106
|
-
subdomain: string;
|
|
107
|
-
port: number;
|
|
108
|
-
}[];
|
|
106
|
+
readonly routes: SandboxRouteData[];
|
|
109
107
|
/**
|
|
110
108
|
* Unique ID of this sandbox.
|
|
111
109
|
*/
|
|
112
|
-
|
|
110
|
+
get sandboxId(): string;
|
|
111
|
+
/**
|
|
112
|
+
* Data about this sandbox.
|
|
113
|
+
*/
|
|
114
|
+
private readonly sandbox;
|
|
113
115
|
/**
|
|
114
116
|
* Create a new sandbox.
|
|
115
117
|
*
|
|
@@ -131,13 +133,10 @@ export declare class Sandbox {
|
|
|
131
133
|
* @param routes - Port-to-subdomain mappings for exposed ports
|
|
132
134
|
* @param sandboxId - Unique identifier for the sandbox
|
|
133
135
|
*/
|
|
134
|
-
constructor({ client, routes,
|
|
136
|
+
constructor({ client, routes, sandbox, }: {
|
|
135
137
|
client: APIClient;
|
|
136
|
-
routes:
|
|
137
|
-
|
|
138
|
-
port: number;
|
|
139
|
-
}[];
|
|
140
|
-
sandboxId: string;
|
|
138
|
+
routes: SandboxRouteData[];
|
|
139
|
+
sandbox: SandboxData;
|
|
141
140
|
});
|
|
142
141
|
/**
|
|
143
142
|
* Get a previously run command by its ID.
|
|
@@ -145,7 +144,7 @@ export declare class Sandbox {
|
|
|
145
144
|
* @param cmdId - ID of the command to retrieve
|
|
146
145
|
* @returns A {@link Command} instance representing the command
|
|
147
146
|
*/
|
|
148
|
-
getCommand(cmdId: string): Command
|
|
147
|
+
getCommand(cmdId: string): Promise<Command>;
|
|
149
148
|
/**
|
|
150
149
|
* Start executing a command in this sandbox.
|
|
151
150
|
*
|
|
@@ -184,6 +183,16 @@ export declare class Sandbox {
|
|
|
184
183
|
* @param path - Path of the directory to create
|
|
185
184
|
*/
|
|
186
185
|
mkDir(path: string): Promise<void>;
|
|
186
|
+
/**
|
|
187
|
+
* Read a file from the filesystem of this sandbox.
|
|
188
|
+
*
|
|
189
|
+
* @param file - File to read, with path and optional cwd
|
|
190
|
+
* @returns A promise that resolves to a ReadableStream containing the file contents
|
|
191
|
+
*/
|
|
192
|
+
readFile(file: {
|
|
193
|
+
path: string;
|
|
194
|
+
cwd?: string;
|
|
195
|
+
}): Promise<NodeJS.ReadableStream | null>;
|
|
187
196
|
/**
|
|
188
197
|
* Write files to the filesystem of this sandbox.
|
|
189
198
|
*
|
package/dist/sandbox.js
CHANGED
|
@@ -11,6 +11,12 @@ const get_credentials_1 = require("./utils/get-credentials");
|
|
|
11
11
|
* @hideconstructor
|
|
12
12
|
*/
|
|
13
13
|
class Sandbox {
|
|
14
|
+
/**
|
|
15
|
+
* Unique ID of this sandbox.
|
|
16
|
+
*/
|
|
17
|
+
get sandboxId() {
|
|
18
|
+
return this.sandbox.id;
|
|
19
|
+
}
|
|
14
20
|
/**
|
|
15
21
|
* Create a new sandbox.
|
|
16
22
|
*
|
|
@@ -33,7 +39,7 @@ class Sandbox {
|
|
|
33
39
|
});
|
|
34
40
|
return new Sandbox({
|
|
35
41
|
client,
|
|
36
|
-
|
|
42
|
+
sandbox: sandbox.json.sandbox,
|
|
37
43
|
routes: sandbox.json.routes,
|
|
38
44
|
});
|
|
39
45
|
}
|
|
@@ -49,10 +55,13 @@ class Sandbox {
|
|
|
49
55
|
teamId: credentials.teamId,
|
|
50
56
|
token: credentials.token,
|
|
51
57
|
});
|
|
58
|
+
const sandbox = await client.getSandbox({
|
|
59
|
+
sandboxId: params.sandboxId,
|
|
60
|
+
});
|
|
52
61
|
return new Sandbox({
|
|
53
62
|
client,
|
|
54
|
-
|
|
55
|
-
routes:
|
|
63
|
+
sandbox: sandbox.json.sandbox,
|
|
64
|
+
routes: sandbox.json.routes,
|
|
56
65
|
});
|
|
57
66
|
}
|
|
58
67
|
/**
|
|
@@ -62,10 +71,10 @@ class Sandbox {
|
|
|
62
71
|
* @param routes - Port-to-subdomain mappings for exposed ports
|
|
63
72
|
* @param sandboxId - Unique identifier for the sandbox
|
|
64
73
|
*/
|
|
65
|
-
constructor({ client, routes,
|
|
74
|
+
constructor({ client, routes, sandbox, }) {
|
|
66
75
|
this.client = client;
|
|
67
76
|
this.routes = routes;
|
|
68
|
-
this.
|
|
77
|
+
this.sandbox = sandbox;
|
|
69
78
|
}
|
|
70
79
|
/**
|
|
71
80
|
* Get a previously run command by its ID.
|
|
@@ -73,11 +82,15 @@ class Sandbox {
|
|
|
73
82
|
* @param cmdId - ID of the command to retrieve
|
|
74
83
|
* @returns A {@link Command} instance representing the command
|
|
75
84
|
*/
|
|
76
|
-
getCommand(cmdId) {
|
|
85
|
+
async getCommand(cmdId) {
|
|
86
|
+
const command = await this.client.getCommand({
|
|
87
|
+
sandboxId: this.sandbox.id,
|
|
88
|
+
cmdId,
|
|
89
|
+
});
|
|
77
90
|
return new command_1.Command({
|
|
78
91
|
client: this.client,
|
|
79
|
-
sandboxId: this.
|
|
80
|
-
|
|
92
|
+
sandboxId: this.sandbox.id,
|
|
93
|
+
cmd: command.json.command,
|
|
81
94
|
});
|
|
82
95
|
}
|
|
83
96
|
async runCommand(commandOrParams, args) {
|
|
@@ -94,7 +107,7 @@ class Sandbox {
|
|
|
94
107
|
*/
|
|
95
108
|
async _runCommand(params) {
|
|
96
109
|
const commandResponse = await this.client.runCommand({
|
|
97
|
-
sandboxId: this.
|
|
110
|
+
sandboxId: this.sandbox.id,
|
|
98
111
|
command: params.cmd,
|
|
99
112
|
args: params.args ?? [],
|
|
100
113
|
cwd: params.cwd,
|
|
@@ -102,8 +115,8 @@ class Sandbox {
|
|
|
102
115
|
});
|
|
103
116
|
const command = new command_1.Command({
|
|
104
117
|
client: this.client,
|
|
105
|
-
sandboxId: this.
|
|
106
|
-
|
|
118
|
+
sandboxId: this.sandbox.id,
|
|
119
|
+
cmd: commandResponse.json.command,
|
|
107
120
|
});
|
|
108
121
|
if (params.stdout || params.stderr) {
|
|
109
122
|
(async () => {
|
|
@@ -126,10 +139,23 @@ class Sandbox {
|
|
|
126
139
|
*/
|
|
127
140
|
async mkDir(path) {
|
|
128
141
|
await this.client.mkDir({
|
|
129
|
-
sandboxId: this.
|
|
142
|
+
sandboxId: this.sandbox.id,
|
|
130
143
|
path: path,
|
|
131
144
|
});
|
|
132
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Read a file from the filesystem of this sandbox.
|
|
148
|
+
*
|
|
149
|
+
* @param file - File to read, with path and optional cwd
|
|
150
|
+
* @returns A promise that resolves to a ReadableStream containing the file contents
|
|
151
|
+
*/
|
|
152
|
+
async readFile(file) {
|
|
153
|
+
return this.client.readFile({
|
|
154
|
+
sandboxId: this.sandbox.id,
|
|
155
|
+
path: file.path,
|
|
156
|
+
cwd: file.cwd,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
133
159
|
/**
|
|
134
160
|
* Write files to the filesystem of this sandbox.
|
|
135
161
|
*
|
|
@@ -138,7 +164,7 @@ class Sandbox {
|
|
|
138
164
|
*/
|
|
139
165
|
async writeFiles(files) {
|
|
140
166
|
return this.client.writeFiles({
|
|
141
|
-
sandboxId: this.
|
|
167
|
+
sandboxId: this.sandbox.id,
|
|
142
168
|
files: files,
|
|
143
169
|
});
|
|
144
170
|
}
|
|
@@ -165,7 +191,7 @@ class Sandbox {
|
|
|
165
191
|
*/
|
|
166
192
|
async stop() {
|
|
167
193
|
await this.client.stopSandbox({
|
|
168
|
-
sandboxId: this.
|
|
194
|
+
sandboxId: this.sandbox.id,
|
|
169
195
|
});
|
|
170
196
|
}
|
|
171
197
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.consumeReadable = consumeReadable;
|
|
4
|
+
/**
|
|
5
|
+
* Consumes a readable entirely concatenating all content in a single Buffer
|
|
6
|
+
* @param readable A Readable stream
|
|
7
|
+
*/
|
|
8
|
+
function consumeReadable(readable) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
readable.on("error", (err) => reject(err));
|
|
12
|
+
readable.on("data", (chunk) => chunks.push(chunk));
|
|
13
|
+
readable.on("end", () => resolve(Buffer.concat(chunks)));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare const linuxSignalMapping: {
|
|
2
|
+
readonly SIGHUP: 1;
|
|
3
|
+
readonly SIGINT: 2;
|
|
4
|
+
readonly SIGQUIT: 3;
|
|
5
|
+
readonly SIGKILL: 9;
|
|
6
|
+
readonly SIGTERM: 15;
|
|
7
|
+
readonly SIGCONT: 18;
|
|
8
|
+
readonly SIGSTOP: 19;
|
|
9
|
+
};
|
|
10
|
+
type CommonLinuxSignals = keyof typeof linuxSignalMapping;
|
|
11
|
+
export type Signal = CommonLinuxSignals | number;
|
|
12
|
+
export declare function resolveSignal(signal: Signal): number;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveSignal = resolveSignal;
|
|
4
|
+
const linuxSignalMapping = {
|
|
5
|
+
SIGHUP: 1,
|
|
6
|
+
SIGINT: 2,
|
|
7
|
+
SIGQUIT: 3,
|
|
8
|
+
SIGKILL: 9,
|
|
9
|
+
SIGTERM: 15,
|
|
10
|
+
SIGCONT: 18,
|
|
11
|
+
SIGSTOP: 19,
|
|
12
|
+
};
|
|
13
|
+
function resolveSignal(signal) {
|
|
14
|
+
if (typeof signal === "number") {
|
|
15
|
+
return signal;
|
|
16
|
+
}
|
|
17
|
+
if (signal in linuxSignalMapping) {
|
|
18
|
+
return linuxSignalMapping[signal];
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Unknown signal name: ${String(signal)}`);
|
|
21
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.0.
|
|
1
|
+
export declare const VERSION = "0.0.10";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -5,18 +5,18 @@ import {
|
|
|
5
5
|
type RequestParams,
|
|
6
6
|
} from "./base-client";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
SandboxAndRoutesResponse,
|
|
9
|
+
SandboxResponse,
|
|
10
|
+
CommandResponse,
|
|
11
|
+
CommandFinishedResponse,
|
|
12
|
+
EmptyResponse,
|
|
12
13
|
LogLine,
|
|
13
|
-
StoppedSandbox,
|
|
14
|
-
WrittenFile,
|
|
15
14
|
} from "./validators";
|
|
16
15
|
import { APIError } from "./api-error";
|
|
17
16
|
import { FileWriter } from "./file-writer";
|
|
18
17
|
import { LRUCache } from "lru-cache";
|
|
19
18
|
import { VERSION } from "../version";
|
|
19
|
+
import { consumeReadable } from "../utils/consume-readable";
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
import jsonlines from "jsonlines";
|
|
22
22
|
import os from "os";
|
|
@@ -56,18 +56,32 @@ export class APIClient extends BaseClient {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
async getSandbox(params: { sandboxId: string }) {
|
|
60
|
+
return parseOrThrow(
|
|
61
|
+
SandboxAndRoutesResponse,
|
|
62
|
+
await this.request(`/v1/sandboxes/${params.sandboxId}`),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
async createSandbox(params: {
|
|
60
67
|
ports?: number[];
|
|
61
68
|
projectId: string;
|
|
62
69
|
source?:
|
|
63
|
-
| {
|
|
70
|
+
| {
|
|
71
|
+
type: "git";
|
|
72
|
+
url: string;
|
|
73
|
+
depth?: number;
|
|
74
|
+
revision?: string;
|
|
75
|
+
username?: string;
|
|
76
|
+
password?: string;
|
|
77
|
+
}
|
|
64
78
|
| { type: "tarball"; url: string };
|
|
65
79
|
timeout?: number;
|
|
66
80
|
resources?: { vcpus: number };
|
|
67
81
|
runtime?: "node22" | "python3.13";
|
|
68
82
|
}) {
|
|
69
83
|
return parseOrThrow(
|
|
70
|
-
|
|
84
|
+
SandboxAndRoutesResponse,
|
|
71
85
|
await this.request("/v1/sandboxes", {
|
|
72
86
|
method: "POST",
|
|
73
87
|
body: JSON.stringify({
|
|
@@ -90,7 +104,7 @@ export class APIClient extends BaseClient {
|
|
|
90
104
|
env: Record<string, string>;
|
|
91
105
|
}) {
|
|
92
106
|
return parseOrThrow(
|
|
93
|
-
|
|
107
|
+
CommandResponse,
|
|
94
108
|
await this.request(`/v1/sandboxes/${params.sandboxId}/cmd`, {
|
|
95
109
|
method: "POST",
|
|
96
110
|
body: JSON.stringify({
|
|
@@ -107,12 +121,12 @@ export class APIClient extends BaseClient {
|
|
|
107
121
|
sandboxId: string;
|
|
108
122
|
cmdId: string;
|
|
109
123
|
wait: true;
|
|
110
|
-
}): Promise<Parsed<z.infer<typeof
|
|
124
|
+
}): Promise<Parsed<z.infer<typeof CommandFinishedResponse>>>;
|
|
111
125
|
async getCommand(params: {
|
|
112
126
|
sandboxId: string;
|
|
113
127
|
cmdId: string;
|
|
114
128
|
wait?: boolean;
|
|
115
|
-
}): Promise<Parsed<z.infer<typeof
|
|
129
|
+
}): Promise<Parsed<z.infer<typeof CommandResponse>>>;
|
|
116
130
|
async getCommand(params: {
|
|
117
131
|
sandboxId: string;
|
|
118
132
|
cmdId: string;
|
|
@@ -120,14 +134,14 @@ export class APIClient extends BaseClient {
|
|
|
120
134
|
}) {
|
|
121
135
|
return params.wait
|
|
122
136
|
? parseOrThrow(
|
|
123
|
-
|
|
137
|
+
CommandFinishedResponse,
|
|
124
138
|
await this.request(
|
|
125
139
|
`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`,
|
|
126
140
|
{ query: { wait: "true" } },
|
|
127
141
|
),
|
|
128
142
|
)
|
|
129
143
|
: parseOrThrow(
|
|
130
|
-
|
|
144
|
+
CommandResponse,
|
|
131
145
|
await this.request(
|
|
132
146
|
`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`,
|
|
133
147
|
),
|
|
@@ -136,7 +150,7 @@ export class APIClient extends BaseClient {
|
|
|
136
150
|
|
|
137
151
|
async mkDir(params: { sandboxId: string; path: string; cwd?: string }) {
|
|
138
152
|
return parseOrThrow(
|
|
139
|
-
|
|
153
|
+
EmptyResponse,
|
|
140
154
|
await this.request(`/v1/sandboxes/${params.sandboxId}/fs/mkdir`, {
|
|
141
155
|
method: "POST",
|
|
142
156
|
body: JSON.stringify({ path: params.path, cwd: params.cwd }),
|
|
@@ -147,11 +161,13 @@ export class APIClient extends BaseClient {
|
|
|
147
161
|
getFileWriter(params: { sandboxId: string }) {
|
|
148
162
|
const writer = new FileWriter();
|
|
149
163
|
return {
|
|
150
|
-
response:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
164
|
+
response: (async () => {
|
|
165
|
+
return this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { "content-type": "application/gzip" },
|
|
168
|
+
body: await consumeReadable(writer.readable),
|
|
169
|
+
});
|
|
170
|
+
})(),
|
|
155
171
|
writer,
|
|
156
172
|
};
|
|
157
173
|
}
|
|
@@ -168,8 +184,8 @@ export class APIClient extends BaseClient {
|
|
|
168
184
|
await writer.addFile({ name: file.path, content: file.stream });
|
|
169
185
|
}
|
|
170
186
|
|
|
171
|
-
|
|
172
|
-
await parseOrThrow(
|
|
187
|
+
writer.end();
|
|
188
|
+
await parseOrThrow(EmptyResponse, await response);
|
|
173
189
|
}
|
|
174
190
|
|
|
175
191
|
async readFile(params: {
|
|
@@ -192,6 +208,23 @@ export class APIClient extends BaseClient {
|
|
|
192
208
|
return response.body;
|
|
193
209
|
}
|
|
194
210
|
|
|
211
|
+
async killCommand(params: {
|
|
212
|
+
sandboxId: string;
|
|
213
|
+
commandId: string;
|
|
214
|
+
signal: number;
|
|
215
|
+
}) {
|
|
216
|
+
return parseOrThrow(
|
|
217
|
+
CommandResponse,
|
|
218
|
+
await this.request(
|
|
219
|
+
`/v1/sandboxes/${params.sandboxId}/${params.commandId}/kill`,
|
|
220
|
+
{
|
|
221
|
+
method: "POST",
|
|
222
|
+
body: JSON.stringify({ signal: params.signal }),
|
|
223
|
+
},
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
195
228
|
async *getLogs(params: {
|
|
196
229
|
sandboxId: string;
|
|
197
230
|
cmdId: string;
|
|
@@ -226,10 +259,10 @@ export class APIClient extends BaseClient {
|
|
|
226
259
|
|
|
227
260
|
async stopSandbox(params: {
|
|
228
261
|
sandboxId: string;
|
|
229
|
-
}): Promise<Parsed<z.infer<typeof
|
|
262
|
+
}): Promise<Parsed<z.infer<typeof SandboxResponse>>> {
|
|
230
263
|
const url = `/v1/sandboxes/${params.sandboxId}/stop`;
|
|
231
264
|
return parseOrThrow(
|
|
232
|
-
|
|
265
|
+
SandboxResponse,
|
|
233
266
|
await this.request(url, { method: "POST" }),
|
|
234
267
|
);
|
|
235
268
|
}
|
package/src/api-client/index.ts
CHANGED
|
@@ -1,36 +1,66 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export type SandboxData = z.infer<typeof Sandbox>;
|
|
4
|
+
|
|
5
|
+
export const Sandbox = z.object({
|
|
6
|
+
id: z.string(),
|
|
7
|
+
memory: z.number(),
|
|
8
|
+
vcpus: z.number(),
|
|
9
|
+
region: z.string(),
|
|
10
|
+
runtime: z.string(),
|
|
11
|
+
timeout: z.number(),
|
|
12
|
+
status: z.enum(["pending", "running", "stopping", "stopped", "failed"]),
|
|
13
|
+
requestedAt: z.number(),
|
|
14
|
+
startedAt: z.number().optional(),
|
|
15
|
+
requestedStopAt: z.number().optional(),
|
|
16
|
+
stoppedAt: z.number().optional(),
|
|
17
|
+
duration: z.number().optional(),
|
|
18
|
+
createdAt: z.number(),
|
|
19
|
+
updatedAt: z.number(),
|
|
6
20
|
});
|
|
7
21
|
|
|
8
|
-
export
|
|
9
|
-
|
|
22
|
+
export type SandboxRouteData = z.infer<typeof SandboxRoute>;
|
|
23
|
+
|
|
24
|
+
export const SandboxRoute = z.object({
|
|
25
|
+
url: z.string(),
|
|
26
|
+
subdomain: z.string(),
|
|
27
|
+
port: z.number(),
|
|
10
28
|
});
|
|
11
29
|
|
|
30
|
+
export type CommandData = z.infer<typeof Command>;
|
|
31
|
+
|
|
12
32
|
export const Command = z.object({
|
|
33
|
+
id: z.string(),
|
|
34
|
+
name: z.string(),
|
|
13
35
|
args: z.array(z.string()),
|
|
14
|
-
cmdId: z.string(),
|
|
15
36
|
cwd: z.string(),
|
|
37
|
+
sandboxId: z.string(),
|
|
16
38
|
exitCode: z.number().nullable(),
|
|
17
|
-
|
|
39
|
+
startedAt: z.number(),
|
|
18
40
|
});
|
|
19
41
|
|
|
20
|
-
|
|
21
|
-
args: z.array(z.string()),
|
|
22
|
-
cmdId: z.string(),
|
|
23
|
-
cwd: z.string(),
|
|
42
|
+
const CommandFinished = Command.extend({
|
|
24
43
|
exitCode: z.number(),
|
|
25
|
-
name: z.string(),
|
|
26
44
|
});
|
|
27
45
|
|
|
28
|
-
export const
|
|
46
|
+
export const SandboxResponse = z.object({
|
|
47
|
+
sandbox: Sandbox,
|
|
48
|
+
});
|
|
29
49
|
|
|
30
|
-
export const
|
|
31
|
-
|
|
50
|
+
export const SandboxAndRoutesResponse = SandboxResponse.extend({
|
|
51
|
+
routes: z.array(SandboxRoute),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const CommandResponse = z.object({
|
|
55
|
+
command: Command,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const CommandFinishedResponse = z.object({
|
|
59
|
+
command: CommandFinished,
|
|
32
60
|
});
|
|
33
61
|
|
|
62
|
+
export const EmptyResponse = z.object({});
|
|
63
|
+
|
|
34
64
|
export const LogLine = z.object({
|
|
35
65
|
stream: z.enum(["stdout", "stderr"]),
|
|
36
66
|
data: z.string(),
|
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
|
});
|