@vercel/sandbox 0.0.13 → 0.0.15

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @vercel/sandbox@0.0.13 build /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
2
+ > @vercel/sandbox@0.0.15 build /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
3
3
  > tsc
4
4
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @vercel/sandbox@0.0.13 typecheck /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
2
+ > @vercel/sandbox@0.0.15 typecheck /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
3
3
  > tsc --noEmit
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @vercel/sandbox
2
2
 
3
+ ## 0.0.15
4
+
5
+ ### Patch Changes
6
+
7
+ - Remove warning when consuming logs more than once ([#91](https://github.com/vercel/sandbox-sdk/pull/91))
8
+
9
+ - Improve future compatibility of runtime parameter ([#88](https://github.com/vercel/sandbox-sdk/pull/88))
10
+
11
+ ## 0.0.14
12
+
13
+ ### Patch Changes
14
+
15
+ - Use `@vercel/oidc` for authentication ([#87](https://github.com/vercel/sandbox-sdk/pull/87))
16
+
17
+ - Expose more data in `Command` ([#85](https://github.com/vercel/sandbox-sdk/pull/85))
18
+
3
19
  ## 0.0.13
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -35,7 +35,52 @@ vercel env pull
35
35
 
36
36
  Now create `next-dev.ts`:
37
37
 
38
- {@includeCode ../cli/src/example-next.ts}
38
+ ```ts
39
+ import ms from "ms";
40
+ import { Sandbox } from "@vercel/sandbox";
41
+ import { setTimeout } from "timers/promises";
42
+ import { spawn } from "child_process";
43
+
44
+ async function main() {
45
+ const sandbox = await Sandbox.create({
46
+ source: {
47
+ url: "https://github.com/vercel/sandbox-example-next.git",
48
+ type: "git",
49
+ },
50
+ resources: { vcpus: 4 },
51
+ timeout: ms("5m"),
52
+ ports: [3000],
53
+ runtime: "node22",
54
+ });
55
+
56
+ console.log(`Installing dependencies...`);
57
+ const install = await sandbox.runCommand({
58
+ cmd: "npm",
59
+ args: ["install", "--loglevel", "info"],
60
+ stderr: process.stderr,
61
+ stdout: process.stdout,
62
+ });
63
+
64
+ if (install.exitCode != 0) {
65
+ console.log("installing packages failed");
66
+ process.exit(1);
67
+ }
68
+
69
+ console.log(`Starting the development server...`);
70
+ await sandbox.runCommand({
71
+ cmd: "npm",
72
+ args: ["run", "dev"],
73
+ stderr: process.stderr,
74
+ stdout: process.stdout,
75
+ detached: true,
76
+ });
77
+
78
+ await setTimeout(500);
79
+ spawn("open", [sandbox.domain(3000)]);
80
+ }
81
+
82
+ main().catch(console.error);
83
+ ```
39
84
 
40
85
  Run it like this:
41
86
 
@@ -53,7 +53,7 @@ export declare class APIClient extends BaseClient {
53
53
  resources?: {
54
54
  vcpus: number;
55
55
  };
56
- runtime?: "node22" | "python3.13";
56
+ runtime?: "node22" | "python3.13" | (string & {});
57
57
  }): Promise<Parsed<{
58
58
  sandbox: {
59
59
  region: string;
@@ -8,20 +8,10 @@ const base_client_1 = require("./base-client");
8
8
  const validators_1 = require("./validators");
9
9
  const api_error_1 = require("./api-error");
10
10
  const file_writer_1 = require("./file-writer");
11
- const lru_cache_1 = require("lru-cache");
12
11
  const version_1 = require("../version");
13
12
  const consume_readable_1 = require("../utils/consume-readable");
14
13
  const jsonlines_1 = __importDefault(require("jsonlines"));
15
14
  const os_1 = __importDefault(require("os"));
16
- const ms_1 = __importDefault(require("ms"));
17
- /**
18
- * Allows to track the logs hits for a command un a to maximum of items and
19
- * TTL so that we don't incur in memory leaks in a log running process.
20
- */
21
- const logHits = new lru_cache_1.LRUCache({
22
- ttl: (0, ms_1.default)("45m"),
23
- max: 1000,
24
- });
25
15
  class APIClient extends base_client_1.BaseClient {
26
16
  constructor(params) {
27
17
  super({
@@ -128,18 +118,6 @@ class APIClient extends base_client_1.BaseClient {
128
118
  message: "Expected a stream of logs",
129
119
  });
130
120
  }
131
- /**
132
- * Currently, once we consume logs in the backend we cannot read them
133
- * again. This logic writes a warning when the endpoint for a command
134
- * logs is consumed more than once to alert the user about this.
135
- *
136
- * This is a temporary solution, we should be able to handle this in
137
- * the backend in the future.
138
- */
139
- if (logHits.get(url)) {
140
- console.warn(`Multiple consumers for logs of command \`${params.cmdId}\`. This may lead to unexpected behavior.`);
141
- }
142
- logHits.set(url, true);
143
121
  for await (const chunk of response.body.pipe(jsonlines_1.default.parse())) {
144
122
  yield validators_1.LogLine.parse(chunk);
145
123
  }
package/dist/command.d.ts CHANGED
@@ -24,10 +24,13 @@ export declare class Command {
24
24
  * Data for the command execution.
25
25
  */
26
26
  private cmd;
27
+ exitCode: number | null;
27
28
  /**
28
29
  * ID of the command execution.
29
30
  */
30
31
  get cmdId(): string;
32
+ get cwd(): string;
33
+ get startedAt(): number;
31
34
  /**
32
35
  * @param params - Object containing the client, sandbox ID, and command ID.
33
36
  * @param params.client - API client used to interact with the backend.
package/dist/command.js CHANGED
@@ -19,6 +19,12 @@ class Command {
19
19
  get cmdId() {
20
20
  return this.cmd.id;
21
21
  }
22
+ get cwd() {
23
+ return this.cmd.cwd;
24
+ }
25
+ get startedAt() {
26
+ return this.cmd.startedAt;
27
+ }
22
28
  /**
23
29
  * @param params - Object containing the client, sandbox ID, and command ID.
24
30
  * @param params.client - API client used to interact with the backend.
@@ -29,6 +35,7 @@ class Command {
29
35
  this.client = client;
30
36
  this.sandboxId = sandboxId;
31
37
  this.cmd = cmd;
38
+ this.exitCode = cmd.exitCode ?? null;
32
39
  }
33
40
  /**
34
41
  * Iterate over the output of this command.
package/dist/sandbox.d.ts CHANGED
@@ -51,7 +51,7 @@ export interface CreateSandboxParams {
51
51
  * The runtime of the sandbox, currently only `node22` and `python3.13` are supported.
52
52
  * If not specified, the default runtime `node22` will be used.
53
53
  */
54
- runtime?: "node22" | "python3.13";
54
+ runtime?: "node22" | "python3.13" | (string & {});
55
55
  }
56
56
  /** @inline */
57
57
  interface GetSandboxParams {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCredentials = getCredentials;
4
- const get_vercel_oidc_token_1 = require("./get-vercel-oidc-token");
4
+ const oidc_1 = require("@vercel/oidc");
5
5
  const decode_base64_url_1 = require("./decode-base64-url");
6
6
  const zod_1 = require("zod");
7
7
  /**
@@ -19,7 +19,7 @@ function getCredentials(params) {
19
19
  if (credentials) {
20
20
  return credentials;
21
21
  }
22
- const oidcToken = (0, get_vercel_oidc_token_1.getVercelOidcToken)();
22
+ const oidcToken = (0, oidc_1.getVercelOidcTokenSync)();
23
23
  if (oidcToken) {
24
24
  return getCredentialsFromOIDCToken(oidcToken);
25
25
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.0.13";
1
+ export declare const VERSION = "0.0.15";
package/dist/version.js CHANGED
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // Autogenerated by inject-version.ts
5
- exports.VERSION = "0.0.13";
5
+ exports.VERSION = "0.0.15";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/sandbox",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,10 +9,10 @@
9
9
  "author": "",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
+ "@vercel/oidc": "^2.0.0",
12
13
  "async-retry": "1.3.3",
13
14
  "form-data": "3.0.0",
14
15
  "jsonlines": "0.1.1",
15
- "lru-cache": "11.1.0",
16
16
  "ms": "2.1.3",
17
17
  "node-fetch": "2.6.11",
18
18
  "tar-stream": "3.1.7",
@@ -14,22 +14,11 @@ import {
14
14
  } from "./validators";
15
15
  import { APIError } from "./api-error";
16
16
  import { FileWriter } from "./file-writer";
17
- import { LRUCache } from "lru-cache";
18
17
  import { VERSION } from "../version";
19
18
  import { consumeReadable } from "../utils/consume-readable";
20
19
  import { z } from "zod";
21
20
  import jsonlines from "jsonlines";
22
21
  import os from "os";
23
- import ms from "ms";
24
-
25
- /**
26
- * Allows to track the logs hits for a command un a to maximum of items and
27
- * TTL so that we don't incur in memory leaks in a log running process.
28
- */
29
- const logHits = new LRUCache<string, boolean>({
30
- ttl: ms("45m"),
31
- max: 1000,
32
- });
33
22
 
34
23
  export class APIClient extends BaseClient {
35
24
  private teamId: string;
@@ -78,7 +67,7 @@ export class APIClient extends BaseClient {
78
67
  | { type: "tarball"; url: string };
79
68
  timeout?: number;
80
69
  resources?: { vcpus: number };
81
- runtime?: "node22" | "python3.13";
70
+ runtime?: "node22" | "python3.13" | (string & {});
82
71
  }) {
83
72
  return parseOrThrow(
84
73
  SandboxAndRoutesResponse,
@@ -239,21 +228,6 @@ export class APIClient extends BaseClient {
239
228
  });
240
229
  }
241
230
 
242
- /**
243
- * Currently, once we consume logs in the backend we cannot read them
244
- * again. This logic writes a warning when the endpoint for a command
245
- * logs is consumed more than once to alert the user about this.
246
- *
247
- * This is a temporary solution, we should be able to handle this in
248
- * the backend in the future.
249
- */
250
- if (logHits.get(url)) {
251
- console.warn(
252
- `Multiple consumers for logs of command \`${params.cmdId}\`. This may lead to unexpected behavior.`,
253
- );
254
- }
255
-
256
- logHits.set(url, true);
257
231
  for await (const chunk of response.body.pipe(jsonlines.parse())) {
258
232
  yield LogLine.parse(chunk);
259
233
  }
@@ -4,19 +4,14 @@ import { Sandbox } from "./sandbox";
4
4
  let sandbox: Sandbox;
5
5
 
6
6
  beforeEach(async () => {
7
- sandbox = await Sandbox.create({
8
- projectId: process.env.VERCEL_PROJECT_ID!,
9
- teamId: process.env.VERCEL_TEAM_ID!,
10
- token: process.env.VERCEL_TOKEN!,
11
- });
7
+ sandbox = await Sandbox.create();
12
8
  });
13
9
 
14
10
  afterEach(async () => {
15
11
  await sandbox.stop();
16
12
  });
17
13
 
18
- it("warns when there is more than one logs consumer", async () => {
19
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
14
+ it("supports more than one logs consumer", async () => {
20
15
  const stdoutSpy = vi
21
16
  .spyOn(process.stdout, "write")
22
17
  .mockImplementation(() => true);
@@ -27,13 +22,8 @@ it("warns when there is more than one logs consumer", async () => {
27
22
  stdout: process.stdout,
28
23
  });
29
24
 
30
- expect(await cmd.stdout()).toEqual("");
25
+ expect(await cmd.stdout()).toEqual("Hello World!\n");
31
26
  expect(stdoutSpy).toHaveBeenCalledWith("Hello World!\n");
32
- expect(warnSpy).toHaveBeenCalledWith(
33
- expect.stringMatching(
34
- /Multiple consumers for logs of command `[^`]+`\.\sThis may lead to unexpected behavior\./,
35
- ),
36
- );
37
27
  });
38
28
 
39
29
  it("does not warn when there is only one logs consumer", async () => {
@@ -89,9 +79,12 @@ it("can execute commands with sudo", async () => {
89
79
  expect(output).toContain("USER=root\n");
90
80
  expect(output).toContain("SUDO_USER=vercel-sandbox\n");
91
81
 
92
- // The actual path contains more, but this is enough
93
- // to verify that we do not reset it to a default path.
94
- expect(output).toContain("PATH=/vercel/bin");
82
+ const pathLine = output.split("\n").find((line) => line.startsWith("PATH="));
83
+ expect(pathLine).toBeDefined();
84
+
85
+ const pathSegments = pathLine!.slice(5).split(":");
86
+ expect(pathSegments).toContain("/vercel/bin");
87
+ expect(pathSegments).toContain("/vercel/runtimes/node22/bin");
95
88
 
96
89
  const dnf = await sandbox.runCommand({
97
90
  cmd: "dnf",
@@ -102,5 +95,5 @@ it("can execute commands with sudo", async () => {
102
95
  expect(dnf.exitCode).toBe(0);
103
96
 
104
97
  const which = await sandbox.runCommand("which", ["go"]);
105
- expect(which.output()).resolves.toContain("/usr/bin/go");
98
+ expect(await which.output()).toContain("/usr/bin/go");
106
99
  });
package/src/command.ts CHANGED
@@ -28,6 +28,8 @@ export class Command {
28
28
  */
29
29
  private cmd: CommandData;
30
30
 
31
+ public exitCode: number | null;
32
+
31
33
  /**
32
34
  * ID of the command execution.
33
35
  */
@@ -35,6 +37,14 @@ export class Command {
35
37
  return this.cmd.id;
36
38
  }
37
39
 
40
+ get cwd() {
41
+ return this.cmd.cwd;
42
+ }
43
+
44
+ get startedAt() {
45
+ return this.cmd.startedAt;
46
+ }
47
+
38
48
  /**
39
49
  * @param params - Object containing the client, sandbox ID, and command ID.
40
50
  * @param params.client - API client used to interact with the backend.
@@ -53,6 +63,7 @@ export class Command {
53
63
  this.client = client;
54
64
  this.sandboxId = sandboxId;
55
65
  this.cmd = cmd;
66
+ this.exitCode = cmd.exitCode ?? null;
56
67
  }
57
68
 
58
69
  /**
@@ -5,11 +5,7 @@ import { Sandbox } from "./sandbox";
5
5
  let sandbox: Sandbox;
6
6
 
7
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
- });
8
+ sandbox = await Sandbox.create();
13
9
  });
14
10
 
15
11
  afterEach(async () => {
@@ -18,8 +14,8 @@ afterEach(async () => {
18
14
 
19
15
  it("allows to write files and then read them", async () => {
20
16
  await sandbox.writeFiles([
21
- { path: "hello1.txt", stream: Buffer.from("Hello 1") },
22
- { path: "hello2.txt", stream: Buffer.from("Hello 2") },
17
+ { path: "hello1.txt", content: Buffer.from("Hello 1") },
18
+ { path: "hello2.txt", content: Buffer.from("Hello 2") },
23
19
  ]);
24
20
 
25
21
  const content1 = await sandbox.readFile({ path: "hello1.txt" });
package/src/sandbox.ts CHANGED
@@ -51,7 +51,7 @@ export interface CreateSandboxParams {
51
51
  * The runtime of the sandbox, currently only `node22` and `python3.13` are supported.
52
52
  * If not specified, the default runtime `node22` will be used.
53
53
  */
54
- runtime?: "node22" | "python3.13";
54
+ runtime?: "node22" | "python3.13" | (string & {});
55
55
  }
56
56
 
57
57
  /** @inline */
@@ -1,4 +1,4 @@
1
- import { getVercelOidcToken } from "./get-vercel-oidc-token";
1
+ import { getVercelOidcTokenSync } from "@vercel/oidc";
2
2
  import { decodeBase64Url } from "./decode-base64-url";
3
3
  import { z } from "zod";
4
4
 
@@ -34,7 +34,7 @@ export function getCredentials<T>(params?: T | Credentials): Credentials {
34
34
  return credentials;
35
35
  }
36
36
 
37
- const oidcToken = getVercelOidcToken();
37
+ const oidcToken = getVercelOidcTokenSync();
38
38
  if (oidcToken) {
39
39
  return getCredentialsFromOIDCToken(oidcToken);
40
40
  }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Autogenerated by inject-version.ts
2
- export const VERSION = "0.0.13";
2
+ export const VERSION = "0.0.15";
package/vercel.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "redirects": [
3
+ {
4
+ "source": "/(.*)",
5
+ "destination": "https://vercel.com/docs/vercel-sandbox",
6
+ "permanent": true
7
+ }
8
+ ]
9
+ }
@@ -1,6 +0,0 @@
1
- /**
2
- * This function is implemented in @vercel/functions but it is asynchronous.
3
- * In order to keep it synchronous, we are implementing it here as well.
4
- * Ideally we should remove this and use the one from @vercel/functions.
5
- */
6
- export declare function getVercelOidcToken(): string | undefined;
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getVercelOidcToken = getVercelOidcToken;
4
- /**
5
- * This function is implemented in @vercel/functions but it is asynchronous.
6
- * In order to keep it synchronous, we are implementing it here as well.
7
- * Ideally we should remove this and use the one from @vercel/functions.
8
- */
9
- function getVercelOidcToken() {
10
- const token = getContext().headers?.["x-vercel-oidc-token"] ??
11
- process.env.VERCEL_OIDC_TOKEN;
12
- if (!token && process.env.NODE_ENV === "production") {
13
- throw new Error(`The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?`);
14
- }
15
- return token;
16
- }
17
- const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
18
- function getContext() {
19
- const fromSymbol = globalThis;
20
- return fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
21
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * This function is implemented in @vercel/functions but it is asynchronous.
3
- * In order to keep it synchronous, we are implementing it here as well.
4
- * Ideally we should remove this and use the one from @vercel/functions.
5
- */
6
- export function getVercelOidcToken(): string | undefined {
7
- const token =
8
- getContext().headers?.["x-vercel-oidc-token"] ??
9
- process.env.VERCEL_OIDC_TOKEN;
10
-
11
- if (!token && process.env.NODE_ENV === "production") {
12
- throw new Error(
13
- `The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?`,
14
- );
15
- }
16
-
17
- return token;
18
- }
19
-
20
- const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
21
-
22
- interface Context {
23
- headers?: Record<string, string>;
24
- }
25
-
26
- function getContext(): Context {
27
- const fromSymbol: typeof globalThis & {
28
- [SYMBOL_FOR_REQ_CONTEXT]?: { get?: () => Context };
29
- } = globalThis;
30
- return fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
31
- }