Skip to content

Commit 1c904b0

Browse files
committed
feat(vercel-sandbox): support new Snapshot.fromSandbox method
1 parent 06c20c7 commit 1c904b0

12 files changed

Lines changed: 149 additions & 8 deletions

File tree

.changeset/pre.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"open-planets-joke",
3232
"public-ears-heal",
3333
"seven-olives-mate",
34+
"shiny-bikes-visit",
3435
"slick-colts-learn",
3536
"some-numbers-arrive",
3637
"sour-rocks-grin",

.changeset/shiny-bikes-visit.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@vercel/sandbox": minor
3+
"sandbox": minor
4+
---
5+
6+
Support --sandbox-snapshot parameter in the CLI, and new Snapshot.fromSandbox(...) method

packages/sandbox/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# sandbox
22

3+
## 3.0.0-beta.18
4+
5+
### Minor Changes
6+
7+
- Support --sandbox-snapshot parameter in the CLI, and new Snapshot.fromSandbox(...) method
8+
9+
### Patch Changes
10+
11+
- Updated dependencies []:
12+
- @vercel/sandbox@2.0.0-beta.16
13+
314
## 3.0.0-beta.17
415

516
### Minor Changes

packages/sandbox/docs/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
## `sandbox --help`
22

33
```
4-
sandbox 3.0.0-beta.17
4+
sandbox 3.0.0-beta.18
55
66
▲ sandbox [options] <command>
77
@@ -84,6 +84,7 @@ Options:
8484
--vcpus <COUNT> Number of vCPUs to allocate (each vCPU includes 2048 MB of memory) [optional]
8585
--publish-port <PORT>, -p=<PORT> Publish sandbox port(s) to DOMAIN.vercel.run
8686
--snapshot, -s <snapshot_id> Start the sandbox from a snapshot ID [optional]
87+
--sandbox-snapshot <name> Start the sandbox from another sandbox's current snapshot [optional]
8788
--env <key=value>, -e=<key=value> Environment variables to set for the command
8889
--tag <key=value>, -t=<key=value> Key-value tags to associate with the sandbox (e.g. --tag env=staging)
8990
--snapshot-expiration <DURATION|none> Default snapshot expiration. Use "none" or 0 for no expiration. Example: 7d, 30d [optional]
@@ -138,6 +139,7 @@ Options:
138139
--vcpus <COUNT> Number of vCPUs to allocate (each vCPU includes 2048 MB of memory) [optional]
139140
--publish-port <PORT>, -p=<PORT> Publish sandbox port(s) to DOMAIN.vercel.run
140141
--snapshot, -s <snapshot_id> Start the sandbox from a snapshot ID [optional]
142+
--sandbox-snapshot <name> Start the sandbox from another sandbox's current snapshot [optional]
141143
--env <key=value>, -e=<key=value> Default environment variables for sandbox commands
142144
--tag <key=value>, -t=<key=value> Key-value tags to associate with the sandbox (e.g. --tag env=staging)
143145
--snapshot-expiration <DURATION|none> Default snapshot expiration. Use "none" or 0 for no expiration. Example: 7d, 30d [optional]

packages/sandbox/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "sandbox",
33
"description": "Command line interface for Vercel Sandbox",
4-
"version": "3.0.0-beta.17",
4+
"version": "3.0.0-beta.18",
55
"scripts": {
66
"clean": "rm -rf node_modules dist",
77
"sandbox": "ts-node ./src/sandbox.ts",

packages/sandbox/src/client.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,17 @@ export const sandboxClient: Pick<typeof Sandbox, "get" | "list" | "create"> = {
2121
),
2222
};
2323

24-
export const snapshotClient: Pick<typeof Snapshot, "get" | "list"> = {
24+
export const snapshotClient: Pick<
25+
typeof Snapshot,
26+
"get" | "list" | "fromSandbox"
27+
> = {
2528
list: (params) =>
2629
withErrorHandling(Snapshot.list({ fetch: fetchWithUserAgent, ...params })),
2730
get: (params) => withErrorHandling(Snapshot.get({ ...params })),
31+
fromSandbox: (name, opts) =>
32+
withErrorHandling(
33+
Snapshot.fromSandbox(name, { fetch: fetchWithUserAgent, ...opts }),
34+
),
2835
};
2936

3037
const fetchWithUserAgent: typeof globalThis.fetch = (input, init) => {

packages/sandbox/src/commands/create.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { timeout } from "../args/timeout";
55
import { vcpus } from "../args/vcpus";
66
import chalk from "chalk";
77
import { scope } from "../args/scope";
8-
import { sandboxClient } from "../client";
8+
import { sandboxClient, snapshotClient } from "../client";
99
import { snapshotId } from "../args/snapshot-id";
10+
import { sandboxName } from "../args/sandbox-name";
1011
import ora from "ora";
1112
import * as Exec from "./exec";
1213
import { networkPolicyArgs } from "../args/network-policy";
@@ -59,6 +60,12 @@ export const args = {
5960
description: "Start the sandbox from a snapshot ID",
6061
type: cmd.optional(snapshotId),
6162
}),
63+
sandboxSnapshot: cmd.option({
64+
long: "sandbox-snapshot",
65+
description:
66+
"Start the sandbox from another sandbox's current snapshot",
67+
type: cmd.optional(sandboxName),
68+
}),
6269
connect: cmd.flag({
6370
long: "connect",
6471
description:
@@ -105,6 +112,7 @@ export const create = cmd.command({
105112
vcpus,
106113
silent,
107114
snapshot,
115+
sandboxSnapshot,
108116
connect,
109117
envVars,
110118
tags,
@@ -114,6 +122,15 @@ export const create = cmd.command({
114122
allowedCIDRs,
115123
deniedCIDRs,
116124
}) {
125+
if (snapshot && sandboxSnapshot) {
126+
throw new Error(
127+
[
128+
`Cannot use --snapshot and --sandbox-snapshot together.`,
129+
`${chalk.bold("hint:")} Pick one source for the new sandbox.`,
130+
].join("\n"),
131+
);
132+
}
133+
117134
const networkPolicy = buildNetworkPolicy({
118135
networkPolicy: networkPolicyMode,
119136
allowedDomains,
@@ -124,11 +141,20 @@ export const create = cmd.command({
124141
const persistent = !nonPersistent
125142
const resources = vcpus ? { vcpus } : undefined;
126143
const tagsObj = Object.keys(tags).length > 0 ? tags : undefined;
144+
const resolvedSnapshot =
145+
snapshot ??
146+
(sandboxSnapshot
147+
? await snapshotClient.fromSandbox(sandboxSnapshot, {
148+
teamId: scope.team,
149+
projectId: scope.project,
150+
token: scope.token,
151+
})
152+
: undefined);
127153
const spinner = silent ? undefined : ora("Creating sandbox...").start();
128-
const sandbox = snapshot
154+
const sandbox = resolvedSnapshot
129155
? await sandboxClient.create({
130156
name,
131-
source: { type: "snapshot", snapshotId: snapshot },
157+
source: { type: "snapshot", snapshotId: resolvedSnapshot },
132158
teamId: scope.team,
133159
projectId: scope.project,
134160
token: scope.token,

packages/vercel-sandbox/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @vercel/sandbox
22

3+
## 2.0.0-beta.16
4+
5+
### Minor Changes
6+
7+
- Support --sandbox-snapshot parameter in the CLI, and new Snapshot.fromSandbox(...) method
8+
39
## 2.0.0-beta.15
410

511
### Minor Changes

packages/vercel-sandbox/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vercel/sandbox",
3-
"version": "2.0.0-beta.15",
3+
"version": "2.0.0-beta.16",
44
"description": "Software Development Kit for Vercel Sandbox",
55
"type": "module",
66
"main": "dist/index.cjs",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { describe, expect, it } from "vitest";
2+
import { Sandbox } from "./sandbox.js";
3+
import { Snapshot } from "./snapshot.js";
4+
5+
describe.skipIf(process.env.RUN_INTEGRATION_TESTS !== "1")(
6+
"Snapshot.fromSandbox",
7+
() => {
8+
it("resolves a snapshot ID that can seed a new sandbox", async () => {
9+
const baseSandbox = await Sandbox.create({ persistent: true });
10+
await baseSandbox.writeFiles([
11+
{ path: "from-base.txt", content: Buffer.from("base content") },
12+
]);
13+
await baseSandbox.stop();
14+
15+
const derived = await Sandbox.create({
16+
source: {
17+
type: "snapshot",
18+
snapshotId: await Snapshot.fromSandbox(baseSandbox.name),
19+
},
20+
});
21+
22+
try {
23+
expect(derived.sourceSnapshotId).toBe(baseSandbox.currentSnapshotId);
24+
const content = await derived.readFileToBuffer({
25+
path: "from-base.txt",
26+
});
27+
expect(content?.toString()).toBe("base content");
28+
} finally {
29+
await derived.stop();
30+
}
31+
});
32+
33+
it("throws when the sandbox has no current snapshot", async () => {
34+
const baseSandbox = await Sandbox.create();
35+
expect(baseSandbox.currentSnapshotId).toBeUndefined();
36+
37+
await expect(Snapshot.fromSandbox(baseSandbox.name)).rejects.toThrow(
38+
/has no current snapshot/,
39+
);
40+
});
41+
},
42+
);

0 commit comments

Comments
 (0)