@vercel/sandbox 1.2.0 → 1.2.1

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.
Files changed (60) hide show
  1. package/dist/api-client/api-client.js +7 -0
  2. package/dist/api-client/api-client.js.map +1 -1
  3. package/dist/version.d.ts +1 -1
  4. package/dist/version.js +1 -1
  5. package/package.json +6 -1
  6. package/.turbo/turbo-build.log +0 -4
  7. package/.turbo/turbo-test.log +0 -24
  8. package/.turbo/turbo-typecheck.log +0 -4
  9. package/CHANGELOG.md +0 -277
  10. package/__mocks__/picocolors.ts +0 -13
  11. package/scripts/inject-version.ts +0 -11
  12. package/src/api-client/api-client.test.ts +0 -228
  13. package/src/api-client/api-client.ts +0 -592
  14. package/src/api-client/api-error.ts +0 -46
  15. package/src/api-client/base-client.ts +0 -171
  16. package/src/api-client/file-writer.ts +0 -90
  17. package/src/api-client/index.ts +0 -2
  18. package/src/api-client/validators.ts +0 -146
  19. package/src/api-client/with-retry.ts +0 -131
  20. package/src/auth/api.ts +0 -31
  21. package/src/auth/error.ts +0 -8
  22. package/src/auth/file.ts +0 -69
  23. package/src/auth/index.ts +0 -9
  24. package/src/auth/infer-scope.test.ts +0 -178
  25. package/src/auth/linked-project.test.ts +0 -86
  26. package/src/auth/linked-project.ts +0 -40
  27. package/src/auth/oauth.ts +0 -333
  28. package/src/auth/poll-for-token.ts +0 -89
  29. package/src/auth/project.ts +0 -92
  30. package/src/auth/zod.ts +0 -16
  31. package/src/command.test.ts +0 -103
  32. package/src/command.ts +0 -287
  33. package/src/constants.ts +0 -1
  34. package/src/index.ts +0 -4
  35. package/src/sandbox.test.ts +0 -171
  36. package/src/sandbox.ts +0 -677
  37. package/src/snapshot.ts +0 -110
  38. package/src/utils/array.ts +0 -15
  39. package/src/utils/consume-readable.ts +0 -12
  40. package/src/utils/decode-base64-url.ts +0 -14
  41. package/src/utils/dev-credentials.test.ts +0 -217
  42. package/src/utils/dev-credentials.ts +0 -196
  43. package/src/utils/get-credentials.test.ts +0 -20
  44. package/src/utils/get-credentials.ts +0 -183
  45. package/src/utils/jwt-expiry.test.ts +0 -125
  46. package/src/utils/jwt-expiry.ts +0 -105
  47. package/src/utils/log.ts +0 -20
  48. package/src/utils/normalizePath.test.ts +0 -114
  49. package/src/utils/normalizePath.ts +0 -33
  50. package/src/utils/resolveSignal.ts +0 -24
  51. package/src/utils/types.test.js +0 -7
  52. package/src/utils/types.ts +0 -23
  53. package/src/version.ts +0 -2
  54. package/test-utils/mock-response.ts +0 -12
  55. package/tsconfig.json +0 -16
  56. package/turbo.json +0 -9
  57. package/typedoc.json +0 -13
  58. package/vercel.json +0 -9
  59. package/vitest.config.ts +0 -9
  60. package/vitest.setup.ts +0 -4
@@ -1,592 +0,0 @@
1
- import {
2
- BaseClient,
3
- parseOrThrow,
4
- type Parsed,
5
- type RequestParams,
6
- } from "./base-client";
7
- import {
8
- CommandFinishedData,
9
- SandboxAndRoutesResponse,
10
- SandboxResponse,
11
- CommandResponse,
12
- CommandFinishedResponse,
13
- EmptyResponse,
14
- LogLine,
15
- LogLineStdout,
16
- LogLineStderr,
17
- SandboxesResponse,
18
- ExtendTimeoutResponse,
19
- SnapshotResponse,
20
- CreateSnapshotResponse,
21
- type CommandData,
22
- } from "./validators";
23
- import { APIError, StreamError } from "./api-error";
24
- import { FileWriter } from "./file-writer";
25
- import { VERSION } from "../version";
26
- import { consumeReadable } from "../utils/consume-readable";
27
- import { z } from "zod";
28
- import jsonlines from "jsonlines";
29
- import os from "os";
30
- import { Readable } from "stream";
31
- import { normalizePath } from "../utils/normalizePath";
32
- import { JwtExpiry } from "../utils/jwt-expiry";
33
- import { getPrivateParams, WithPrivate } from "../utils/types";
34
- import { RUNTIMES } from "../constants";
35
-
36
- export interface WithFetchOptions {
37
- fetch?: typeof globalThis.fetch;
38
- }
39
-
40
- export class APIClient extends BaseClient {
41
- private teamId: string;
42
- private tokenExpiry: JwtExpiry | null;
43
-
44
- constructor(params: {
45
- baseUrl?: string;
46
- teamId: string;
47
- token: string;
48
- fetch?: typeof globalThis.fetch;
49
- }) {
50
- super({
51
- baseUrl: params.baseUrl ?? "https://vercel.com/api",
52
- token: params.token,
53
- debug: false,
54
- fetch: params.fetch,
55
- });
56
-
57
- this.teamId = params.teamId;
58
- this.tokenExpiry = JwtExpiry.fromToken(params.token);
59
- }
60
-
61
- private async ensureValidToken(): Promise<void> {
62
- if (!this.tokenExpiry) {
63
- return;
64
- }
65
-
66
- const newExpiry = await this.tokenExpiry.tryRefresh();
67
- if (!newExpiry) {
68
- return;
69
- }
70
-
71
- this.tokenExpiry = newExpiry;
72
- this.token = this.tokenExpiry.token;
73
- if (this.tokenExpiry.payload) {
74
- this.teamId = this.tokenExpiry.payload?.owner_id;
75
- }
76
- }
77
-
78
- protected async request(path: string, params?: RequestParams) {
79
- await this.ensureValidToken();
80
-
81
- return super.request(path, {
82
- ...params,
83
- query: { teamId: this.teamId, ...params?.query },
84
- headers: {
85
- "content-type": "application/json",
86
- "user-agent": `vercel/sandbox/${VERSION} (Node.js/${process.version}; ${os.platform()}/${os.arch()})`,
87
- ...params?.headers,
88
- },
89
- });
90
- }
91
-
92
- async getSandbox(
93
- params: WithPrivate<{ sandboxId: string; signal?: AbortSignal }>,
94
- ) {
95
- const privateParams = getPrivateParams(params);
96
- let querystring = new URLSearchParams(privateParams).toString();
97
- querystring = querystring ? `?${querystring}` : "";
98
- return parseOrThrow(
99
- SandboxAndRoutesResponse,
100
- await this.request(`/v1/sandboxes/${params.sandboxId}${querystring}`, {
101
- signal: params.signal,
102
- }),
103
- );
104
- }
105
-
106
- async createSandbox(
107
- params: WithPrivate<{
108
- ports?: number[];
109
- projectId: string;
110
- source?:
111
- | {
112
- type: "git";
113
- url: string;
114
- depth?: number;
115
- revision?: string;
116
- username?: string;
117
- password?: string;
118
- }
119
- | { type: "tarball"; url: string }
120
- | { type: "snapshot"; snapshotId: string };
121
- timeout?: number;
122
- resources?: { vcpus: number };
123
- runtime?: RUNTIMES | (string & {});
124
- signal?: AbortSignal;
125
- }>,
126
- ) {
127
- const privateParams = getPrivateParams(params);
128
- return parseOrThrow(
129
- SandboxAndRoutesResponse,
130
- await this.request("/v1/sandboxes", {
131
- method: "POST",
132
- body: JSON.stringify({
133
- projectId: params.projectId,
134
- ports: params.ports,
135
- source: params.source,
136
- timeout: params.timeout,
137
- resources: params.resources,
138
- runtime: params.runtime,
139
- ...privateParams,
140
- }),
141
- signal: params.signal,
142
- }),
143
- );
144
- }
145
-
146
- async runCommand(params: {
147
- sandboxId: string;
148
- cwd?: string;
149
- command: string;
150
- args: string[];
151
- env: Record<string, string>;
152
- sudo: boolean;
153
- wait: true;
154
- signal?: AbortSignal;
155
- }): Promise<{ command: CommandData; finished: Promise<CommandFinishedData> }>;
156
- async runCommand(params: {
157
- sandboxId: string;
158
- cwd?: string;
159
- command: string;
160
- args: string[];
161
- env: Record<string, string>;
162
- sudo: boolean;
163
- wait?: false;
164
- signal?: AbortSignal;
165
- }): Promise<Parsed<z.infer<typeof CommandResponse>>>;
166
- async runCommand(params: {
167
- sandboxId: string;
168
- cwd?: string;
169
- command: string;
170
- args: string[];
171
- env: Record<string, string>;
172
- sudo: boolean;
173
- wait?: boolean;
174
- signal?: AbortSignal;
175
- }) {
176
- if (params.wait) {
177
- const response = await this.request(
178
- `/v1/sandboxes/${params.sandboxId}/cmd`,
179
- {
180
- method: "POST",
181
- body: JSON.stringify({
182
- command: params.command,
183
- args: params.args,
184
- cwd: params.cwd,
185
- env: params.env,
186
- sudo: params.sudo,
187
- wait: true,
188
- }),
189
- signal: params.signal,
190
- },
191
- );
192
-
193
- if (response.headers.get("content-type") !== "application/x-ndjson") {
194
- throw new APIError(response, {
195
- message: "Expected a stream of command data",
196
- sandboxId: params.sandboxId,
197
- });
198
- }
199
-
200
- if (response.body === null) {
201
- throw new APIError(response, {
202
- message: "No response body",
203
- sandboxId: params.sandboxId,
204
- });
205
- }
206
-
207
- const jsonlinesStream = jsonlines.parse();
208
- pipe(response.body, jsonlinesStream).catch((err) => {
209
- console.error("Error piping command stream:", err);
210
- });
211
-
212
- const iterator = jsonlinesStream[Symbol.asyncIterator]();
213
- const commandChunk = await iterator.next();
214
- const { command } = CommandResponse.parse(commandChunk.value);
215
-
216
- const finished = (async () => {
217
- const finishedChunk = await iterator.next();
218
- const { command } = CommandFinishedResponse.parse(finishedChunk.value);
219
- return command;
220
- })();
221
-
222
- return { command, finished };
223
- }
224
-
225
- return parseOrThrow(
226
- CommandResponse,
227
- await this.request(`/v1/sandboxes/${params.sandboxId}/cmd`, {
228
- method: "POST",
229
- body: JSON.stringify({
230
- command: params.command,
231
- args: params.args,
232
- cwd: params.cwd,
233
- env: params.env,
234
- sudo: params.sudo,
235
- }),
236
- signal: params.signal,
237
- }),
238
- );
239
- }
240
-
241
- async getCommand(params: {
242
- sandboxId: string;
243
- cmdId: string;
244
- wait: true;
245
- signal?: AbortSignal;
246
- }): Promise<Parsed<z.infer<typeof CommandFinishedResponse>>>;
247
- async getCommand(params: {
248
- sandboxId: string;
249
- cmdId: string;
250
- wait?: boolean;
251
- signal?: AbortSignal;
252
- }): Promise<Parsed<z.infer<typeof CommandResponse>>>;
253
- async getCommand(params: {
254
- sandboxId: string;
255
- cmdId: string;
256
- wait?: boolean;
257
- signal?: AbortSignal;
258
- }) {
259
- return params.wait
260
- ? parseOrThrow(
261
- CommandFinishedResponse,
262
- await this.request(
263
- `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`,
264
- { signal: params.signal, query: { wait: "true" } },
265
- ),
266
- )
267
- : parseOrThrow(
268
- CommandResponse,
269
- await this.request(
270
- `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`,
271
- { signal: params.signal },
272
- ),
273
- );
274
- }
275
-
276
- async mkDir(params: {
277
- sandboxId: string;
278
- path: string;
279
- cwd?: string;
280
- signal?: AbortSignal;
281
- }) {
282
- return parseOrThrow(
283
- EmptyResponse,
284
- await this.request(`/v1/sandboxes/${params.sandboxId}/fs/mkdir`, {
285
- method: "POST",
286
- body: JSON.stringify({ path: params.path, cwd: params.cwd }),
287
- signal: params.signal,
288
- }),
289
- );
290
- }
291
-
292
- getFileWriter(params: {
293
- sandboxId: string;
294
- extractDir: string;
295
- signal?: AbortSignal;
296
- }) {
297
- const writer = new FileWriter();
298
- return {
299
- response: (async () => {
300
- return this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
301
- method: "POST",
302
- headers: {
303
- "content-type": "application/gzip",
304
- "x-cwd": params.extractDir,
305
- },
306
- body: await consumeReadable(writer.readable),
307
- signal: params.signal,
308
- });
309
- })(),
310
- writer,
311
- };
312
- }
313
-
314
- async listSandboxes(params: {
315
- /**
316
- * The ID or name of the project to which the sandboxes belong.
317
- * @example "my-project"
318
- */
319
- projectId: string;
320
- /**
321
- * Maximum number of sandboxes to list from a request.
322
- * @example 10
323
- */
324
- limit?: number;
325
- /**
326
- * Get sandboxes created after this JavaScript timestamp.
327
- * @example 1540095775941
328
- */
329
- since?: number | Date;
330
- /**
331
- * Get sandboxes created before this JavaScript timestamp.
332
- * @example 1540095775951
333
- */
334
- until?: number | Date;
335
- signal?: AbortSignal;
336
- }) {
337
- return parseOrThrow(
338
- SandboxesResponse,
339
- await this.request(`/v1/sandboxes`, {
340
- query: {
341
- project: params.projectId,
342
- limit: params.limit,
343
- since:
344
- typeof params.since === "number"
345
- ? params.since
346
- : params.since?.getTime(),
347
- until:
348
- typeof params.until === "number"
349
- ? params.until
350
- : params.until?.getTime(),
351
- },
352
- method: "GET",
353
- signal: params.signal,
354
- }),
355
- );
356
- }
357
-
358
- async writeFiles(params: {
359
- sandboxId: string;
360
- cwd: string;
361
- files: { path: string; content: Buffer }[];
362
- extractDir: string;
363
- signal?: AbortSignal;
364
- }) {
365
- const { writer, response } = this.getFileWriter({
366
- sandboxId: params.sandboxId,
367
- extractDir: params.extractDir,
368
- signal: params.signal,
369
- });
370
-
371
- for (const file of params.files) {
372
- await writer.addFile({
373
- name: normalizePath({
374
- filePath: file.path,
375
- extractDir: params.extractDir,
376
- cwd: params.cwd,
377
- }),
378
- content: file.content,
379
- });
380
- }
381
-
382
- writer.end();
383
- await parseOrThrow(EmptyResponse, await response);
384
- }
385
-
386
- async readFile(params: {
387
- sandboxId: string;
388
- path: string;
389
- cwd?: string;
390
- signal?: AbortSignal;
391
- }): Promise<Readable | null> {
392
- const response = await this.request(
393
- `/v1/sandboxes/${params.sandboxId}/fs/read`,
394
- {
395
- method: "POST",
396
- body: JSON.stringify({ path: params.path, cwd: params.cwd }),
397
- signal: params.signal,
398
- },
399
- );
400
-
401
- if (response.status === 404) {
402
- return null;
403
- }
404
-
405
- if (response.body === null) {
406
- return null;
407
- }
408
-
409
- return Readable.fromWeb(response.body);
410
- }
411
-
412
- async killCommand(params: {
413
- sandboxId: string;
414
- commandId: string;
415
- signal: number;
416
- abortSignal?: AbortSignal;
417
- }) {
418
- return parseOrThrow(
419
- CommandResponse,
420
- await this.request(
421
- `/v1/sandboxes/${params.sandboxId}/${params.commandId}/kill`,
422
- {
423
- method: "POST",
424
- body: JSON.stringify({ signal: params.signal }),
425
- signal: params.abortSignal,
426
- },
427
- ),
428
- );
429
- }
430
-
431
- getLogs(params: {
432
- sandboxId: string;
433
- cmdId: string;
434
- signal?: AbortSignal;
435
- }): AsyncGenerator<
436
- z.infer<typeof LogLineStdout> | z.infer<typeof LogLineStderr>,
437
- void,
438
- void
439
- > &
440
- Disposable & { close(): void } {
441
- const self = this;
442
- const disposer = new AbortController();
443
- const signal = !params.signal
444
- ? disposer.signal
445
- : mergeSignals(params.signal, disposer.signal);
446
-
447
- const generator = (async function* () {
448
- const url = `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}/logs`;
449
- const response = await self.request(url, {
450
- method: "GET",
451
- signal,
452
- });
453
- if (response.headers.get("content-type") !== "application/x-ndjson") {
454
- throw new APIError(response, {
455
- message: "Expected a stream of logs",
456
- sandboxId: params.sandboxId,
457
- });
458
- }
459
-
460
- if (response.body === null) {
461
- throw new APIError(response, {
462
- message: "No response body",
463
- sandboxId: params.sandboxId,
464
- });
465
- }
466
-
467
- const jsonlinesStream = jsonlines.parse();
468
- pipe(response.body, jsonlinesStream).catch((err) => {
469
- console.error("Error piping logs:", err);
470
- });
471
-
472
- for await (const chunk of jsonlinesStream) {
473
- const parsed = LogLine.parse(chunk);
474
- if (parsed.stream === "error") {
475
- throw new StreamError(
476
- parsed.data.code,
477
- parsed.data.message,
478
- params.sandboxId,
479
- );
480
- }
481
- yield parsed;
482
- }
483
- })();
484
-
485
- return Object.assign(generator, {
486
- [Symbol.dispose]() {
487
- disposer.abort("Disposed");
488
- },
489
- close: () => disposer.abort("Disposed"),
490
- });
491
- }
492
-
493
- async stopSandbox(params: {
494
- sandboxId: string;
495
- signal?: AbortSignal;
496
- }): Promise<Parsed<z.infer<typeof SandboxResponse>>> {
497
- const url = `/v1/sandboxes/${params.sandboxId}/stop`;
498
- return parseOrThrow(
499
- SandboxResponse,
500
- await this.request(url, { method: "POST", signal: params.signal }),
501
- );
502
- }
503
-
504
- async extendTimeout(params: {
505
- sandboxId: string;
506
- duration: number;
507
- signal?: AbortSignal;
508
- }): Promise<Parsed<z.infer<typeof ExtendTimeoutResponse>>> {
509
- const url = `/v1/sandboxes/${params.sandboxId}/extend-timeout`;
510
- return parseOrThrow(
511
- ExtendTimeoutResponse,
512
- await this.request(url, {
513
- method: "POST",
514
- body: JSON.stringify({ duration: params.duration }),
515
- signal: params.signal,
516
- }),
517
- );
518
- }
519
-
520
- async createSnapshot(params: {
521
- sandboxId: string;
522
- signal?: AbortSignal;
523
- }): Promise<Parsed<z.infer<typeof CreateSnapshotResponse>>> {
524
- const url = `/v1/sandboxes/${params.sandboxId}/snapshot`;
525
- return parseOrThrow(
526
- CreateSnapshotResponse,
527
- await this.request(url, { method: "POST", signal: params.signal }),
528
- );
529
- }
530
-
531
- async deleteSnapshot(params: {
532
- snapshotId: string;
533
- signal?: AbortSignal;
534
- }): Promise<Parsed<z.infer<typeof SnapshotResponse>>> {
535
- const url = `/v1/sandboxes/snapshots/${params.snapshotId}`;
536
- return parseOrThrow(
537
- SnapshotResponse,
538
- await this.request(url, { method: "DELETE", signal: params.signal }),
539
- );
540
- }
541
-
542
- async getSnapshot(params: {
543
- snapshotId: string;
544
- signal?: AbortSignal;
545
- }): Promise<Parsed<z.infer<typeof SnapshotResponse>>> {
546
- const url = `/v1/sandboxes/snapshots/${params.snapshotId}`;
547
- return parseOrThrow(
548
- SnapshotResponse,
549
- await this.request(url, { signal: params.signal }),
550
- );
551
- }
552
- }
553
-
554
- async function pipe(
555
- readable: ReadableStream<Uint8Array>,
556
- output: NodeJS.WritableStream,
557
- ) {
558
- const reader = readable.getReader();
559
- try {
560
- while (true) {
561
- const read = await reader.read();
562
- if (read.value) {
563
- output.write(Buffer.from(read.value));
564
- }
565
- if (read.done) {
566
- break;
567
- }
568
- }
569
- } catch (err) {
570
- output.emit("error", err);
571
- } finally {
572
- output.end();
573
- }
574
- }
575
-
576
- function mergeSignals(...signals: [AbortSignal, ...AbortSignal[]]) {
577
- const controller = new AbortController();
578
- const onAbort = () => {
579
- controller.abort();
580
- for (const signal of signals) {
581
- signal.removeEventListener("abort", onAbort);
582
- }
583
- };
584
- for (const signal of signals) {
585
- if (signal.aborted) {
586
- controller.abort();
587
- break;
588
- }
589
- signal.addEventListener("abort", onAbort);
590
- }
591
- return controller.signal;
592
- }
@@ -1,46 +0,0 @@
1
- interface Options<ErrorData> {
2
- message?: string;
3
- json?: ErrorData;
4
- text?: string;
5
- sandboxId?: string;
6
- }
7
-
8
- export class APIError<ErrorData> extends Error {
9
- public response: Response;
10
- public message: string;
11
- public json?: ErrorData;
12
- public text?: string;
13
- public sandboxId?: string;
14
-
15
- constructor(response: Response, options?: Options<ErrorData>) {
16
- super(response.statusText);
17
- if (Error.captureStackTrace) {
18
- Error.captureStackTrace(this, APIError);
19
- }
20
-
21
- this.response = response;
22
- this.message = options?.message ?? "";
23
- this.json = options?.json;
24
- this.text = options?.text;
25
- this.sandboxId = options?.sandboxId;
26
- }
27
- }
28
-
29
- /**
30
- * Error thrown when a stream error is received streaming.
31
- * This typically occurs when the sandbox is stopped while streaming.
32
- */
33
- export class StreamError extends Error {
34
- public code: string;
35
- public sandboxId: string;
36
-
37
- constructor(code: string, message: string, sandboxId: string) {
38
- super(message);
39
- this.name = "StreamError";
40
- this.code = code;
41
- this.sandboxId = sandboxId;
42
- if (Error.captureStackTrace) {
43
- Error.captureStackTrace(this, StreamError);
44
- }
45
- }
46
- }