@vercel/sandbox 0.0.16 → 0.0.17
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 +10 -0
- package/dist/api-client/api-client.d.ts +5 -0
- package/dist/api-client/api-client.js +14 -2
- package/dist/api-client/validators.d.ts +13 -0
- package/dist/api-client/validators.js +1 -0
- package/dist/command.d.ts +26 -7
- package/dist/command.js +26 -5
- package/dist/sandbox.d.ts +5 -2
- package/dist/sandbox.js +4 -0
- package/dist/utils/normalizePath.d.ts +17 -0
- package/dist/utils/normalizePath.js +31 -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 +17 -3
- package/src/api-client/validators.ts +1 -0
- package/src/command.ts +29 -7
- package/src/sandbox.test.ts +42 -1
- package/src/sandbox.ts +6 -1
- package/src/utils/normalizePath.test.ts +114 -0
- package/src/utils/normalizePath.ts +33 -0
- package/src/version.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @vercel/sandbox
|
|
2
2
|
|
|
3
|
+
## 0.0.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Sandboxes can now expose up to 4 ports ([#99](https://github.com/vercel/sandbox-sdk/pull/99))
|
|
8
|
+
|
|
9
|
+
This applies to all SDK versions, but this SDK release documents the limit.
|
|
10
|
+
|
|
11
|
+
- Fix bug in Sandbox.writeFile, add support for absolute paths ([#97](https://github.com/vercel/sandbox-sdk/pull/97))
|
|
12
|
+
|
|
3
13
|
## 0.0.16
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
|
@@ -23,6 +23,7 @@ export declare class APIClient extends BaseClient {
|
|
|
23
23
|
timeout: number;
|
|
24
24
|
requestedAt: number;
|
|
25
25
|
createdAt: number;
|
|
26
|
+
cwd: string;
|
|
26
27
|
updatedAt: number;
|
|
27
28
|
duration?: number | undefined;
|
|
28
29
|
startedAt?: number | undefined;
|
|
@@ -65,6 +66,7 @@ export declare class APIClient extends BaseClient {
|
|
|
65
66
|
timeout: number;
|
|
66
67
|
requestedAt: number;
|
|
67
68
|
createdAt: number;
|
|
69
|
+
cwd: string;
|
|
68
70
|
updatedAt: number;
|
|
69
71
|
duration?: number | undefined;
|
|
70
72
|
startedAt?: number | undefined;
|
|
@@ -112,16 +114,19 @@ export declare class APIClient extends BaseClient {
|
|
|
112
114
|
}): Promise<Parsed<{}>>;
|
|
113
115
|
getFileWriter(params: {
|
|
114
116
|
sandboxId: string;
|
|
117
|
+
extractDir: string;
|
|
115
118
|
}): {
|
|
116
119
|
response: Promise<Response>;
|
|
117
120
|
writer: FileWriter;
|
|
118
121
|
};
|
|
119
122
|
writeFiles(params: {
|
|
120
123
|
sandboxId: string;
|
|
124
|
+
cwd: string;
|
|
121
125
|
files: {
|
|
122
126
|
path: string;
|
|
123
127
|
content: Buffer;
|
|
124
128
|
}[];
|
|
129
|
+
extractDir: string;
|
|
125
130
|
}): Promise<void>;
|
|
126
131
|
readFile(params: {
|
|
127
132
|
sandboxId: string;
|
|
@@ -13,6 +13,7 @@ const consume_readable_1 = require("../utils/consume-readable");
|
|
|
13
13
|
const jsonlines_1 = __importDefault(require("jsonlines"));
|
|
14
14
|
const os_1 = __importDefault(require("os"));
|
|
15
15
|
const stream_1 = require("stream");
|
|
16
|
+
const normalizePath_1 = require("../utils/normalizePath");
|
|
16
17
|
class APIClient extends base_client_1.BaseClient {
|
|
17
18
|
constructor(params) {
|
|
18
19
|
super({
|
|
@@ -78,7 +79,10 @@ class APIClient extends base_client_1.BaseClient {
|
|
|
78
79
|
response: (async () => {
|
|
79
80
|
return this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
|
|
80
81
|
method: "POST",
|
|
81
|
-
headers: {
|
|
82
|
+
headers: {
|
|
83
|
+
"content-type": "application/gzip",
|
|
84
|
+
"x-cwd": params.extractDir,
|
|
85
|
+
},
|
|
82
86
|
body: await (0, consume_readable_1.consumeReadable)(writer.readable),
|
|
83
87
|
});
|
|
84
88
|
})(),
|
|
@@ -88,9 +92,17 @@ class APIClient extends base_client_1.BaseClient {
|
|
|
88
92
|
async writeFiles(params) {
|
|
89
93
|
const { writer, response } = this.getFileWriter({
|
|
90
94
|
sandboxId: params.sandboxId,
|
|
95
|
+
extractDir: params.extractDir,
|
|
91
96
|
});
|
|
92
97
|
for (const file of params.files) {
|
|
93
|
-
await writer.addFile({
|
|
98
|
+
await writer.addFile({
|
|
99
|
+
name: (0, normalizePath_1.normalizePath)({
|
|
100
|
+
filePath: file.path,
|
|
101
|
+
extractDir: params.extractDir,
|
|
102
|
+
cwd: params.cwd,
|
|
103
|
+
}),
|
|
104
|
+
content: file.content,
|
|
105
|
+
});
|
|
94
106
|
}
|
|
95
107
|
writer.end();
|
|
96
108
|
await (0, base_client_1.parseOrThrow)(validators_1.EmptyResponse, await response);
|
|
@@ -14,6 +14,7 @@ export declare const Sandbox: z.ZodObject<{
|
|
|
14
14
|
stoppedAt: z.ZodOptional<z.ZodNumber>;
|
|
15
15
|
duration: z.ZodOptional<z.ZodNumber>;
|
|
16
16
|
createdAt: z.ZodNumber;
|
|
17
|
+
cwd: z.ZodString;
|
|
17
18
|
updatedAt: z.ZodNumber;
|
|
18
19
|
}, "strip", z.ZodTypeAny, {
|
|
19
20
|
region: string;
|
|
@@ -25,6 +26,7 @@ export declare const Sandbox: z.ZodObject<{
|
|
|
25
26
|
timeout: number;
|
|
26
27
|
requestedAt: number;
|
|
27
28
|
createdAt: number;
|
|
29
|
+
cwd: string;
|
|
28
30
|
updatedAt: number;
|
|
29
31
|
duration?: number | undefined;
|
|
30
32
|
startedAt?: number | undefined;
|
|
@@ -40,6 +42,7 @@ export declare const Sandbox: z.ZodObject<{
|
|
|
40
42
|
timeout: number;
|
|
41
43
|
requestedAt: number;
|
|
42
44
|
createdAt: number;
|
|
45
|
+
cwd: string;
|
|
43
46
|
updatedAt: number;
|
|
44
47
|
duration?: number | undefined;
|
|
45
48
|
startedAt?: number | undefined;
|
|
@@ -101,6 +104,7 @@ export declare const SandboxResponse: z.ZodObject<{
|
|
|
101
104
|
stoppedAt: z.ZodOptional<z.ZodNumber>;
|
|
102
105
|
duration: z.ZodOptional<z.ZodNumber>;
|
|
103
106
|
createdAt: z.ZodNumber;
|
|
107
|
+
cwd: z.ZodString;
|
|
104
108
|
updatedAt: z.ZodNumber;
|
|
105
109
|
}, "strip", z.ZodTypeAny, {
|
|
106
110
|
region: string;
|
|
@@ -112,6 +116,7 @@ export declare const SandboxResponse: z.ZodObject<{
|
|
|
112
116
|
timeout: number;
|
|
113
117
|
requestedAt: number;
|
|
114
118
|
createdAt: number;
|
|
119
|
+
cwd: string;
|
|
115
120
|
updatedAt: number;
|
|
116
121
|
duration?: number | undefined;
|
|
117
122
|
startedAt?: number | undefined;
|
|
@@ -127,6 +132,7 @@ export declare const SandboxResponse: z.ZodObject<{
|
|
|
127
132
|
timeout: number;
|
|
128
133
|
requestedAt: number;
|
|
129
134
|
createdAt: number;
|
|
135
|
+
cwd: string;
|
|
130
136
|
updatedAt: number;
|
|
131
137
|
duration?: number | undefined;
|
|
132
138
|
startedAt?: number | undefined;
|
|
@@ -144,6 +150,7 @@ export declare const SandboxResponse: z.ZodObject<{
|
|
|
144
150
|
timeout: number;
|
|
145
151
|
requestedAt: number;
|
|
146
152
|
createdAt: number;
|
|
153
|
+
cwd: string;
|
|
147
154
|
updatedAt: number;
|
|
148
155
|
duration?: number | undefined;
|
|
149
156
|
startedAt?: number | undefined;
|
|
@@ -161,6 +168,7 @@ export declare const SandboxResponse: z.ZodObject<{
|
|
|
161
168
|
timeout: number;
|
|
162
169
|
requestedAt: number;
|
|
163
170
|
createdAt: number;
|
|
171
|
+
cwd: string;
|
|
164
172
|
updatedAt: number;
|
|
165
173
|
duration?: number | undefined;
|
|
166
174
|
startedAt?: number | undefined;
|
|
@@ -183,6 +191,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
|
|
|
183
191
|
stoppedAt: z.ZodOptional<z.ZodNumber>;
|
|
184
192
|
duration: z.ZodOptional<z.ZodNumber>;
|
|
185
193
|
createdAt: z.ZodNumber;
|
|
194
|
+
cwd: z.ZodString;
|
|
186
195
|
updatedAt: z.ZodNumber;
|
|
187
196
|
}, "strip", z.ZodTypeAny, {
|
|
188
197
|
region: string;
|
|
@@ -194,6 +203,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
|
|
|
194
203
|
timeout: number;
|
|
195
204
|
requestedAt: number;
|
|
196
205
|
createdAt: number;
|
|
206
|
+
cwd: string;
|
|
197
207
|
updatedAt: number;
|
|
198
208
|
duration?: number | undefined;
|
|
199
209
|
startedAt?: number | undefined;
|
|
@@ -209,6 +219,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
|
|
|
209
219
|
timeout: number;
|
|
210
220
|
requestedAt: number;
|
|
211
221
|
createdAt: number;
|
|
222
|
+
cwd: string;
|
|
212
223
|
updatedAt: number;
|
|
213
224
|
duration?: number | undefined;
|
|
214
225
|
startedAt?: number | undefined;
|
|
@@ -240,6 +251,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
|
|
|
240
251
|
timeout: number;
|
|
241
252
|
requestedAt: number;
|
|
242
253
|
createdAt: number;
|
|
254
|
+
cwd: string;
|
|
243
255
|
updatedAt: number;
|
|
244
256
|
duration?: number | undefined;
|
|
245
257
|
startedAt?: number | undefined;
|
|
@@ -262,6 +274,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
|
|
|
262
274
|
timeout: number;
|
|
263
275
|
requestedAt: number;
|
|
264
276
|
createdAt: number;
|
|
277
|
+
cwd: string;
|
|
265
278
|
updatedAt: number;
|
|
266
279
|
duration?: number | undefined;
|
|
267
280
|
startedAt?: number | undefined;
|
package/dist/command.d.ts
CHANGED
|
@@ -3,8 +3,11 @@ import { Signal } from "./utils/resolveSignal";
|
|
|
3
3
|
/**
|
|
4
4
|
* A command executed in a Sandbox.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* For detached commands, you can {@link wait} to get a {@link CommandFinished} instance
|
|
7
|
+
* with the populated exit code. For non-detached commands, {@link Sandbox.runCommand}
|
|
8
|
+
* automatically waits and returns a {@link CommandFinished} instance.
|
|
9
|
+
*
|
|
10
|
+
* You can iterate over command output with {@link logs}.
|
|
8
11
|
*
|
|
9
12
|
* @see {@link Sandbox.runCommand} to start a command.
|
|
10
13
|
*
|
|
@@ -67,9 +70,14 @@ export declare class Command {
|
|
|
67
70
|
/**
|
|
68
71
|
* Wait for a command to exit and populate its exit code.
|
|
69
72
|
*
|
|
73
|
+
* This method is useful for detached commands where you need to wait
|
|
74
|
+
* for completion. For non-detached commands, {@link Sandbox.runCommand}
|
|
75
|
+
* automatically waits and returns a {@link CommandFinished} instance.
|
|
76
|
+
*
|
|
70
77
|
* ```
|
|
71
|
-
* await
|
|
72
|
-
*
|
|
78
|
+
* const detachedCmd = await sandbox.runCommand({ cmd: 'sleep', args: ['5'], detached: true });
|
|
79
|
+
* const result = await detachedCmd.wait();
|
|
80
|
+
* if (result.exitCode !== 0) {
|
|
73
81
|
* console.error("Something went wrong...")
|
|
74
82
|
* }
|
|
75
83
|
* ```
|
|
@@ -117,14 +125,16 @@ export declare class Command {
|
|
|
117
125
|
/**
|
|
118
126
|
* A command that has finished executing.
|
|
119
127
|
*
|
|
120
|
-
*
|
|
128
|
+
* The exit code is immediately available and populated upon creation.
|
|
129
|
+
* Unlike {@link Command}, you don't need to call wait() - the command
|
|
130
|
+
* has already completed execution.
|
|
121
131
|
*
|
|
122
132
|
* @hideconstructor
|
|
123
133
|
*/
|
|
124
134
|
export declare class CommandFinished extends Command {
|
|
125
135
|
/**
|
|
126
|
-
* The exit code of the command
|
|
127
|
-
*
|
|
136
|
+
* The exit code of the command. This is always populated for
|
|
137
|
+
* CommandFinished instances.
|
|
128
138
|
*/
|
|
129
139
|
exitCode: number;
|
|
130
140
|
/**
|
|
@@ -140,4 +150,13 @@ export declare class CommandFinished extends Command {
|
|
|
140
150
|
cmd: CommandData;
|
|
141
151
|
exitCode: number;
|
|
142
152
|
});
|
|
153
|
+
/**
|
|
154
|
+
* The wait method is not needed for CommandFinished instances since
|
|
155
|
+
* the command has already completed and exitCode is populated.
|
|
156
|
+
*
|
|
157
|
+
* @deprecated This method is redundant for CommandFinished instances.
|
|
158
|
+
* The exitCode is already available.
|
|
159
|
+
* @returns This CommandFinished instance.
|
|
160
|
+
*/
|
|
161
|
+
wait(): Promise<CommandFinished>;
|
|
143
162
|
}
|
package/dist/command.js
CHANGED
|
@@ -5,8 +5,11 @@ const resolveSignal_1 = require("./utils/resolveSignal");
|
|
|
5
5
|
/**
|
|
6
6
|
* A command executed in a Sandbox.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* For detached commands, you can {@link wait} to get a {@link CommandFinished} instance
|
|
9
|
+
* with the populated exit code. For non-detached commands, {@link Sandbox.runCommand}
|
|
10
|
+
* automatically waits and returns a {@link CommandFinished} instance.
|
|
11
|
+
*
|
|
12
|
+
* You can iterate over command output with {@link logs}.
|
|
10
13
|
*
|
|
11
14
|
* @see {@link Sandbox.runCommand} to start a command.
|
|
12
15
|
*
|
|
@@ -64,9 +67,14 @@ class Command {
|
|
|
64
67
|
/**
|
|
65
68
|
* Wait for a command to exit and populate its exit code.
|
|
66
69
|
*
|
|
70
|
+
* This method is useful for detached commands where you need to wait
|
|
71
|
+
* for completion. For non-detached commands, {@link Sandbox.runCommand}
|
|
72
|
+
* automatically waits and returns a {@link CommandFinished} instance.
|
|
73
|
+
*
|
|
67
74
|
* ```
|
|
68
|
-
* await
|
|
69
|
-
*
|
|
75
|
+
* const detachedCmd = await sandbox.runCommand({ cmd: 'sleep', args: ['5'], detached: true });
|
|
76
|
+
* const result = await detachedCmd.wait();
|
|
77
|
+
* if (result.exitCode !== 0) {
|
|
70
78
|
* console.error("Something went wrong...")
|
|
71
79
|
* }
|
|
72
80
|
* ```
|
|
@@ -145,7 +153,9 @@ exports.Command = Command;
|
|
|
145
153
|
/**
|
|
146
154
|
* A command that has finished executing.
|
|
147
155
|
*
|
|
148
|
-
*
|
|
156
|
+
* The exit code is immediately available and populated upon creation.
|
|
157
|
+
* Unlike {@link Command}, you don't need to call wait() - the command
|
|
158
|
+
* has already completed execution.
|
|
149
159
|
*
|
|
150
160
|
* @hideconstructor
|
|
151
161
|
*/
|
|
@@ -161,5 +171,16 @@ class CommandFinished extends Command {
|
|
|
161
171
|
super({ ...params });
|
|
162
172
|
this.exitCode = params.exitCode;
|
|
163
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* The wait method is not needed for CommandFinished instances since
|
|
176
|
+
* the command has already completed and exitCode is populated.
|
|
177
|
+
*
|
|
178
|
+
* @deprecated This method is redundant for CommandFinished instances.
|
|
179
|
+
* The exitCode is already available.
|
|
180
|
+
* @returns This CommandFinished instance.
|
|
181
|
+
*/
|
|
182
|
+
async wait() {
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
164
185
|
}
|
|
165
186
|
exports.CommandFinished = CommandFinished;
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -31,7 +31,8 @@ export interface CreateSandboxParams {
|
|
|
31
31
|
url: string;
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
|
-
* Array of port numbers to expose from the sandbox.
|
|
34
|
+
* Array of port numbers to expose from the sandbox. Sandboxes can
|
|
35
|
+
* expose up to 4 ports.
|
|
35
36
|
*/
|
|
36
37
|
ports?: number[];
|
|
37
38
|
/**
|
|
@@ -184,7 +185,7 @@ export declare class Sandbox {
|
|
|
184
185
|
* @returns A {@link Command} or {@link CommandFinished}, depending on `detached`.
|
|
185
186
|
* @internal
|
|
186
187
|
*/
|
|
187
|
-
_runCommand(params: RunCommandParams): Promise<
|
|
188
|
+
_runCommand(params: RunCommandParams): Promise<Command | CommandFinished>;
|
|
188
189
|
/**
|
|
189
190
|
* Create a directory in the filesystem of this sandbox.
|
|
190
191
|
*
|
|
@@ -203,6 +204,8 @@ export declare class Sandbox {
|
|
|
203
204
|
}): Promise<NodeJS.ReadableStream | null>;
|
|
204
205
|
/**
|
|
205
206
|
* Write files to the filesystem of this sandbox.
|
|
207
|
+
* Defaults to writing to /vercel/sandbox unless an absolute path is specified.
|
|
208
|
+
* Writes files using the `vercel-sandbox` user.
|
|
206
209
|
*
|
|
207
210
|
* @param files - Array of files with path and stream/buffer contents
|
|
208
211
|
* @returns A promise that resolves when the files are written
|
package/dist/sandbox.js
CHANGED
|
@@ -165,6 +165,8 @@ class Sandbox {
|
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
167
|
* Write files to the filesystem of this sandbox.
|
|
168
|
+
* Defaults to writing to /vercel/sandbox unless an absolute path is specified.
|
|
169
|
+
* Writes files using the `vercel-sandbox` user.
|
|
168
170
|
*
|
|
169
171
|
* @param files - Array of files with path and stream/buffer contents
|
|
170
172
|
* @returns A promise that resolves when the files are written
|
|
@@ -172,6 +174,8 @@ class Sandbox {
|
|
|
172
174
|
async writeFiles(files) {
|
|
173
175
|
return this.client.writeFiles({
|
|
174
176
|
sandboxId: this.sandbox.id,
|
|
177
|
+
cwd: this.sandbox.cwd,
|
|
178
|
+
extractDir: "/",
|
|
175
179
|
files: files,
|
|
176
180
|
});
|
|
177
181
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a path and make it relative to `params.extractDir` for inclusion
|
|
3
|
+
* in our tar archives.
|
|
4
|
+
*
|
|
5
|
+
* Relative paths are first resolved to `params.cwd`.
|
|
6
|
+
* Absolute paths are normalized and resolved relative to `params.extractDir`.
|
|
7
|
+
*
|
|
8
|
+
* In addition, paths are normalized so consecutive slashes are removed and
|
|
9
|
+
* stuff like `../..` is resolved appropriately.
|
|
10
|
+
*
|
|
11
|
+
* This function always returns a path relative to `params.extractDir`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function normalizePath(params: {
|
|
14
|
+
filePath: string;
|
|
15
|
+
cwd: string;
|
|
16
|
+
extractDir: string;
|
|
17
|
+
}): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.normalizePath = normalizePath;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
/**
|
|
9
|
+
* Normalize a path and make it relative to `params.extractDir` for inclusion
|
|
10
|
+
* in our tar archives.
|
|
11
|
+
*
|
|
12
|
+
* Relative paths are first resolved to `params.cwd`.
|
|
13
|
+
* Absolute paths are normalized and resolved relative to `params.extractDir`.
|
|
14
|
+
*
|
|
15
|
+
* In addition, paths are normalized so consecutive slashes are removed and
|
|
16
|
+
* stuff like `../..` is resolved appropriately.
|
|
17
|
+
*
|
|
18
|
+
* This function always returns a path relative to `params.extractDir`.
|
|
19
|
+
*/
|
|
20
|
+
function normalizePath(params) {
|
|
21
|
+
if (!path_1.default.posix.isAbsolute(params.cwd)) {
|
|
22
|
+
throw new Error("cwd dir must be absolute");
|
|
23
|
+
}
|
|
24
|
+
if (!path_1.default.posix.isAbsolute(params.extractDir)) {
|
|
25
|
+
throw new Error("extractDir must be absolute");
|
|
26
|
+
}
|
|
27
|
+
const basePath = path_1.default.posix.isAbsolute(params.filePath)
|
|
28
|
+
? path_1.default.posix.normalize(params.filePath)
|
|
29
|
+
: path_1.default.posix.join(params.cwd, params.filePath);
|
|
30
|
+
return path_1.default.posix.relative(params.extractDir, basePath);
|
|
31
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.0.
|
|
1
|
+
export declare const VERSION = "0.0.17";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ import { z } from "zod";
|
|
|
20
20
|
import jsonlines from "jsonlines";
|
|
21
21
|
import os from "os";
|
|
22
22
|
import { Readable } from "stream";
|
|
23
|
+
import { normalizePath } from "../utils/normalizePath";
|
|
23
24
|
|
|
24
25
|
export class APIClient extends BaseClient {
|
|
25
26
|
private teamId: string;
|
|
@@ -150,13 +151,16 @@ export class APIClient extends BaseClient {
|
|
|
150
151
|
);
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
getFileWriter(params: { sandboxId: string }) {
|
|
154
|
+
getFileWriter(params: { sandboxId: string; extractDir: string }) {
|
|
154
155
|
const writer = new FileWriter();
|
|
155
156
|
return {
|
|
156
157
|
response: (async () => {
|
|
157
158
|
return this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
|
|
158
159
|
method: "POST",
|
|
159
|
-
headers: {
|
|
160
|
+
headers: {
|
|
161
|
+
"content-type": "application/gzip",
|
|
162
|
+
"x-cwd": params.extractDir,
|
|
163
|
+
},
|
|
160
164
|
body: await consumeReadable(writer.readable),
|
|
161
165
|
});
|
|
162
166
|
})(),
|
|
@@ -166,14 +170,24 @@ export class APIClient extends BaseClient {
|
|
|
166
170
|
|
|
167
171
|
async writeFiles(params: {
|
|
168
172
|
sandboxId: string;
|
|
173
|
+
cwd: string;
|
|
169
174
|
files: { path: string; content: Buffer }[];
|
|
175
|
+
extractDir: string;
|
|
170
176
|
}) {
|
|
171
177
|
const { writer, response } = this.getFileWriter({
|
|
172
178
|
sandboxId: params.sandboxId,
|
|
179
|
+
extractDir: params.extractDir,
|
|
173
180
|
});
|
|
174
181
|
|
|
175
182
|
for (const file of params.files) {
|
|
176
|
-
await writer.addFile({
|
|
183
|
+
await writer.addFile({
|
|
184
|
+
name: normalizePath({
|
|
185
|
+
filePath: file.path,
|
|
186
|
+
extractDir: params.extractDir,
|
|
187
|
+
cwd: params.cwd,
|
|
188
|
+
}),
|
|
189
|
+
content: file.content,
|
|
190
|
+
});
|
|
177
191
|
}
|
|
178
192
|
|
|
179
193
|
writer.end();
|
package/src/command.ts
CHANGED
|
@@ -4,8 +4,11 @@ import { Signal, resolveSignal } from "./utils/resolveSignal";
|
|
|
4
4
|
/**
|
|
5
5
|
* A command executed in a Sandbox.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* For detached commands, you can {@link wait} to get a {@link CommandFinished} instance
|
|
8
|
+
* with the populated exit code. For non-detached commands, {@link Sandbox.runCommand}
|
|
9
|
+
* automatically waits and returns a {@link CommandFinished} instance.
|
|
10
|
+
*
|
|
11
|
+
* You can iterate over command output with {@link logs}.
|
|
9
12
|
*
|
|
10
13
|
* @see {@link Sandbox.runCommand} to start a command.
|
|
11
14
|
*
|
|
@@ -94,9 +97,14 @@ export class Command {
|
|
|
94
97
|
/**
|
|
95
98
|
* Wait for a command to exit and populate its exit code.
|
|
96
99
|
*
|
|
100
|
+
* This method is useful for detached commands where you need to wait
|
|
101
|
+
* for completion. For non-detached commands, {@link Sandbox.runCommand}
|
|
102
|
+
* automatically waits and returns a {@link CommandFinished} instance.
|
|
103
|
+
*
|
|
97
104
|
* ```
|
|
98
|
-
* await
|
|
99
|
-
*
|
|
105
|
+
* const detachedCmd = await sandbox.runCommand({ cmd: 'sleep', args: ['5'], detached: true });
|
|
106
|
+
* const result = await detachedCmd.wait();
|
|
107
|
+
* if (result.exitCode !== 0) {
|
|
100
108
|
* console.error("Something went wrong...")
|
|
101
109
|
* }
|
|
102
110
|
* ```
|
|
@@ -180,14 +188,16 @@ export class Command {
|
|
|
180
188
|
/**
|
|
181
189
|
* A command that has finished executing.
|
|
182
190
|
*
|
|
183
|
-
*
|
|
191
|
+
* The exit code is immediately available and populated upon creation.
|
|
192
|
+
* Unlike {@link Command}, you don't need to call wait() - the command
|
|
193
|
+
* has already completed execution.
|
|
184
194
|
*
|
|
185
195
|
* @hideconstructor
|
|
186
196
|
*/
|
|
187
197
|
export class CommandFinished extends Command {
|
|
188
198
|
/**
|
|
189
|
-
* The exit code of the command
|
|
190
|
-
*
|
|
199
|
+
* The exit code of the command. This is always populated for
|
|
200
|
+
* CommandFinished instances.
|
|
191
201
|
*/
|
|
192
202
|
public exitCode: number;
|
|
193
203
|
|
|
@@ -207,4 +217,16 @@ export class CommandFinished extends Command {
|
|
|
207
217
|
super({ ...params });
|
|
208
218
|
this.exitCode = params.exitCode;
|
|
209
219
|
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The wait method is not needed for CommandFinished instances since
|
|
223
|
+
* the command has already completed and exitCode is populated.
|
|
224
|
+
*
|
|
225
|
+
* @deprecated This method is redundant for CommandFinished instances.
|
|
226
|
+
* The exitCode is already available.
|
|
227
|
+
* @returns This CommandFinished instance.
|
|
228
|
+
*/
|
|
229
|
+
async wait(): Promise<CommandFinished> {
|
|
230
|
+
return this;
|
|
231
|
+
}
|
|
210
232
|
}
|
package/src/sandbox.test.ts
CHANGED
|
@@ -2,10 +2,11 @@ import { it, beforeEach, afterEach, expect } from "vitest";
|
|
|
2
2
|
import { consumeReadable } from "./utils/consume-readable";
|
|
3
3
|
import { Sandbox } from "./sandbox";
|
|
4
4
|
|
|
5
|
+
const PORTS = [3000, 4000];
|
|
5
6
|
let sandbox: Sandbox;
|
|
6
7
|
|
|
7
8
|
beforeEach(async () => {
|
|
8
|
-
sandbox = await Sandbox.create();
|
|
9
|
+
sandbox = await Sandbox.create({ ports: PORTS });
|
|
9
10
|
});
|
|
10
11
|
|
|
11
12
|
afterEach(async () => {
|
|
@@ -23,3 +24,43 @@ it("allows to write files and then read them", async () => {
|
|
|
23
24
|
expect((await consumeReadable(content1!)).toString()).toBe("Hello 1");
|
|
24
25
|
expect((await consumeReadable(content2!)).toString()).toBe("Hello 2");
|
|
25
26
|
});
|
|
27
|
+
|
|
28
|
+
it("verifies port forwarding works correctly", async () => {
|
|
29
|
+
const serverScript = `
|
|
30
|
+
const http = require('http');
|
|
31
|
+
const ports = process.argv.slice(2);
|
|
32
|
+
|
|
33
|
+
for (const port of ports) {
|
|
34
|
+
const server = http.createServer((req, res) => {
|
|
35
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
36
|
+
res.end(\`hello port \${port}\`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
server.listen(port, () => {
|
|
40
|
+
console.log(\`Server running on port \${port}\`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
await sandbox.writeFiles([
|
|
46
|
+
{ path: "server.js", content: Buffer.from(serverScript) },
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const server = await sandbox.runCommand({
|
|
50
|
+
cmd: "node",
|
|
51
|
+
args: ["server.js", ...PORTS.map(String)],
|
|
52
|
+
detached: true,
|
|
53
|
+
stdout: process.stdout,
|
|
54
|
+
stderr: process.stderr,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
58
|
+
|
|
59
|
+
for (const port of PORTS) {
|
|
60
|
+
const response = await fetch(sandbox.domain(port));
|
|
61
|
+
const text = await response.text();
|
|
62
|
+
expect(text).toBe(`hello port ${port}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await server.kill();
|
|
66
|
+
});
|
package/src/sandbox.ts
CHANGED
|
@@ -32,7 +32,8 @@ export interface CreateSandboxParams {
|
|
|
32
32
|
}
|
|
33
33
|
| { type: "tarball"; url: string };
|
|
34
34
|
/**
|
|
35
|
-
* Array of port numbers to expose from the sandbox.
|
|
35
|
+
* Array of port numbers to expose from the sandbox. Sandboxes can
|
|
36
|
+
* expose up to 4 ports.
|
|
36
37
|
*/
|
|
37
38
|
ports?: number[];
|
|
38
39
|
/**
|
|
@@ -331,6 +332,8 @@ export class Sandbox {
|
|
|
331
332
|
|
|
332
333
|
/**
|
|
333
334
|
* Write files to the filesystem of this sandbox.
|
|
335
|
+
* Defaults to writing to /vercel/sandbox unless an absolute path is specified.
|
|
336
|
+
* Writes files using the `vercel-sandbox` user.
|
|
334
337
|
*
|
|
335
338
|
* @param files - Array of files with path and stream/buffer contents
|
|
336
339
|
* @returns A promise that resolves when the files are written
|
|
@@ -338,6 +341,8 @@ export class Sandbox {
|
|
|
338
341
|
async writeFiles(files: { path: string; content: Buffer }[]) {
|
|
339
342
|
return this.client.writeFiles({
|
|
340
343
|
sandboxId: this.sandbox.id,
|
|
344
|
+
cwd: this.sandbox.cwd,
|
|
345
|
+
extractDir: "/",
|
|
341
346
|
files: files,
|
|
342
347
|
});
|
|
343
348
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { normalizePath } from "./normalizePath";
|
|
3
|
+
|
|
4
|
+
describe("normalizePath", () => {
|
|
5
|
+
it("handles base cases", () => {
|
|
6
|
+
expect(
|
|
7
|
+
normalizePath({
|
|
8
|
+
filePath: "foo.txt",
|
|
9
|
+
cwd: "/vercel/sandbox",
|
|
10
|
+
extractDir: "/",
|
|
11
|
+
}),
|
|
12
|
+
).toBe("vercel/sandbox/foo.txt");
|
|
13
|
+
expect(
|
|
14
|
+
normalizePath({
|
|
15
|
+
filePath: "foo/bar/baz.txt",
|
|
16
|
+
cwd: "/vercel/sandbox",
|
|
17
|
+
extractDir: "/",
|
|
18
|
+
}),
|
|
19
|
+
).toBe("vercel/sandbox/foo/bar/baz.txt");
|
|
20
|
+
expect(
|
|
21
|
+
normalizePath({ filePath: "bar.txt", cwd: "/", extractDir: "/" }),
|
|
22
|
+
).toBe("bar.txt");
|
|
23
|
+
expect(
|
|
24
|
+
normalizePath({
|
|
25
|
+
filePath: "/some/other/dir/foo.txt",
|
|
26
|
+
cwd: "/bar",
|
|
27
|
+
extractDir: "/",
|
|
28
|
+
}),
|
|
29
|
+
).toBe("some/other/dir/foo.txt");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("handles base cases (extract dir)", () => {
|
|
33
|
+
expect(
|
|
34
|
+
normalizePath({
|
|
35
|
+
filePath: "foo.txt",
|
|
36
|
+
cwd: "/vercel/sandbox",
|
|
37
|
+
extractDir: "/vercel",
|
|
38
|
+
}),
|
|
39
|
+
).toBe("sandbox/foo.txt");
|
|
40
|
+
expect(
|
|
41
|
+
normalizePath({
|
|
42
|
+
filePath: "foo/bar/baz.txt",
|
|
43
|
+
cwd: "/vercel/sandbox",
|
|
44
|
+
extractDir: "/vercel",
|
|
45
|
+
}),
|
|
46
|
+
).toBe("sandbox/foo/bar/baz.txt");
|
|
47
|
+
|
|
48
|
+
// TODO: Should this be allowed?
|
|
49
|
+
expect(
|
|
50
|
+
normalizePath({ filePath: "bar.txt", cwd: "/", extractDir: "/vercel" }),
|
|
51
|
+
).toBe("../bar.txt");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("handles normalization", () => {
|
|
55
|
+
expect(
|
|
56
|
+
normalizePath({
|
|
57
|
+
filePath: "/resolves/../this/stuff/foo.txt",
|
|
58
|
+
cwd: "/bar",
|
|
59
|
+
extractDir: "/",
|
|
60
|
+
}),
|
|
61
|
+
).toBe("this/stuff/foo.txt");
|
|
62
|
+
expect(
|
|
63
|
+
normalizePath({
|
|
64
|
+
filePath: "/handles//extra-slashes",
|
|
65
|
+
cwd: "/",
|
|
66
|
+
extractDir: "/",
|
|
67
|
+
}),
|
|
68
|
+
).toBe("handles/extra-slashes");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("resolves relative paths", () => {
|
|
72
|
+
expect(
|
|
73
|
+
normalizePath({
|
|
74
|
+
filePath: "/../../../foo.txt",
|
|
75
|
+
cwd: "/",
|
|
76
|
+
extractDir: "/",
|
|
77
|
+
}),
|
|
78
|
+
).toBe("foo.txt");
|
|
79
|
+
expect(
|
|
80
|
+
normalizePath({
|
|
81
|
+
filePath: "../../../../foo.txt",
|
|
82
|
+
cwd: "/vercel/sandbox",
|
|
83
|
+
extractDir: "/",
|
|
84
|
+
}),
|
|
85
|
+
).toBe("foo.txt");
|
|
86
|
+
expect(
|
|
87
|
+
normalizePath({
|
|
88
|
+
filePath: "../foo.txt",
|
|
89
|
+
cwd: "/vercel/sandbox",
|
|
90
|
+
extractDir: "/",
|
|
91
|
+
}),
|
|
92
|
+
).toBe("vercel/foo.txt");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("validates the cwd", () => {
|
|
96
|
+
expect(() => {
|
|
97
|
+
normalizePath({
|
|
98
|
+
filePath: "doesn't matter",
|
|
99
|
+
cwd: "relative/root",
|
|
100
|
+
extractDir: "/",
|
|
101
|
+
});
|
|
102
|
+
}).toThrow("cwd dir must be absolute");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("validates the cwd", () => {
|
|
106
|
+
expect(() => {
|
|
107
|
+
normalizePath({
|
|
108
|
+
filePath: "doesn't matter",
|
|
109
|
+
cwd: "/",
|
|
110
|
+
extractDir: "some/relative/path",
|
|
111
|
+
});
|
|
112
|
+
}).toThrow("extractDir must be absolute");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a path and make it relative to `params.extractDir` for inclusion
|
|
5
|
+
* in our tar archives.
|
|
6
|
+
*
|
|
7
|
+
* Relative paths are first resolved to `params.cwd`.
|
|
8
|
+
* Absolute paths are normalized and resolved relative to `params.extractDir`.
|
|
9
|
+
*
|
|
10
|
+
* In addition, paths are normalized so consecutive slashes are removed and
|
|
11
|
+
* stuff like `../..` is resolved appropriately.
|
|
12
|
+
*
|
|
13
|
+
* This function always returns a path relative to `params.extractDir`.
|
|
14
|
+
*/
|
|
15
|
+
export function normalizePath(params: {
|
|
16
|
+
filePath: string;
|
|
17
|
+
cwd: string;
|
|
18
|
+
extractDir: string;
|
|
19
|
+
}) {
|
|
20
|
+
if (!path.posix.isAbsolute(params.cwd)) {
|
|
21
|
+
throw new Error("cwd dir must be absolute");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!path.posix.isAbsolute(params.extractDir)) {
|
|
25
|
+
throw new Error("extractDir must be absolute");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const basePath = path.posix.isAbsolute(params.filePath)
|
|
29
|
+
? path.posix.normalize(params.filePath)
|
|
30
|
+
: path.posix.join(params.cwd, params.filePath);
|
|
31
|
+
|
|
32
|
+
return path.posix.relative(params.extractDir, basePath);
|
|
33
|
+
}
|
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.17";
|