Skip to content

Commit ce57736

Browse files
New best practices release (#191)
* fix: Default to public-hosts privacy. Unlisted is a security concern. Will not break default behavior as end users should never open Sandboxes in workspace of SDK user * fix: Warn about creating sandboxes from sandboxes * chore: deprecate automatic lifecycle management related config * fix: make user (session) creation optional and deprecate it * fix: properly handle optional id when getting session * fix: ignore files should be part of template * fix: deprecate hibernation timeout * feat: delete method to delete Sandboxes * chore: remove deprecation indications, we'll rely on docs for now
1 parent 3f14843 commit ce57736

38 files changed

+4439
-5350
lines changed

openapi.json

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,16 @@
10621062
"enum": ["browser", "vm"],
10631063
"type": "string"
10641064
},
1065+
"settings": {
1066+
"description": "Sandbox settings.",
1067+
"properties": {
1068+
"use_pint": {
1069+
"description": "Whether to use Pint for the sandbox.",
1070+
"type": "boolean"
1071+
}
1072+
},
1073+
"type": "object"
1074+
},
10651075
"tags": {
10661076
"default": [],
10671077
"description": "List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags.",
@@ -1217,6 +1227,33 @@
12171227
"title": "TokenCreateResponse",
12181228
"type": "object"
12191229
},
1230+
"VMDeleteResponse": {
1231+
"allOf": [
1232+
{
1233+
"properties": {
1234+
"errors": {
1235+
"items": {
1236+
"oneOf": [
1237+
{ "type": "string" },
1238+
{ "additionalProperties": true, "type": "object" }
1239+
],
1240+
"title": "Error"
1241+
},
1242+
"type": "array"
1243+
},
1244+
"success": { "type": "boolean" }
1245+
},
1246+
"title": "Response",
1247+
"type": "object"
1248+
},
1249+
{
1250+
"properties": { "data": { "properties": {}, "type": "object" } },
1251+
"type": "object"
1252+
}
1253+
],
1254+
"title": "VMDeleteResponse",
1255+
"type": "object"
1256+
},
12201257
"TemplateCreateResponse": {
12211258
"allOf": [
12221259
{
@@ -2234,6 +2271,36 @@
22342271
"tags": ["vm"]
22352272
}
22362273
},
2274+
"/vm/{id}": {
2275+
"delete": {
2276+
"callbacks": {},
2277+
"description": "Deletes a VM, permanently removing it from the system.\n\nThis endpoint can only be used on VMs that belong to your team's workspace.\nDeleting a VM is irreversible and will permanently delete all data associated with it.\n",
2278+
"operationId": "vm/delete",
2279+
"parameters": [
2280+
{
2281+
"description": "Sandbox ID",
2282+
"example": "new",
2283+
"in": "path",
2284+
"name": "id",
2285+
"required": true,
2286+
"schema": { "type": "string" }
2287+
}
2288+
],
2289+
"responses": {
2290+
"200": {
2291+
"content": {
2292+
"application/json": {
2293+
"schema": { "$ref": "#/components/schemas/VMDeleteResponse" }
2294+
}
2295+
},
2296+
"description": "VM Delete Response"
2297+
}
2298+
},
2299+
"security": [{ "authorization": ["vm:manage"] }],
2300+
"summary": "Delete a VM",
2301+
"tags": ["vm"]
2302+
}
2303+
},
22372304
"/vm/{id}/hibernate": {
22382305
"post": {
22392306
"callbacks": {},
@@ -2604,6 +2671,6 @@
26042671
}
26052672
},
26062673
"security": [],
2607-
"servers": [{ "url": "https://api.codesandbox.io", "variables": {} }],
2674+
"servers": [{ "url": "https://api.codesandbox.stream", "variables": {} }],
26082675
"tags": []
26092676
}

src/API.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
vmListClusters,
2020
vmListRunningVms,
2121
vmCreateTag,
22+
vmDelete,
2223
vmHibernate,
2324
vmUpdateHibernationTimeout,
2425
vmCreateSession,
@@ -42,6 +43,7 @@ import type {
4243
TemplatesCreateData,
4344
VmAssignTagAliasData,
4445
VmCreateTagData,
46+
VmDeleteData,
4547
VmHibernateData,
4648
VmUpdateHibernationTimeoutData,
4749
VmCreateSessionData,
@@ -247,6 +249,14 @@ export class API {
247249
return handleResponse(response, "Failed to create VM tag");
248250
}
249251

252+
async deleteVm(id: string) {
253+
const response = await vmDelete({
254+
client: this.client,
255+
path: { id },
256+
});
257+
return handleResponse(response, `Failed to delete VM ${id}`);
258+
}
259+
250260
async hibernate(id: string, data?: VmHibernateData["body"]) {
251261
return retryWithDelay(
252262
async () => {

src/Sandbox.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,11 @@ export class Sandbox {
178178
pitcherManagerResponse: PitcherManagerResponse,
179179
customSession?: SessionCreateOptions
180180
): Promise<SandboxSession> {
181-
if (!customSession) {
181+
if (!customSession || !customSession.id) {
182182
return {
183183
sandboxId: this.id,
184184
bootupType: this.bootupType,
185+
hostToken: customSession?.hostToken,
185186
cluster: this.cluster,
186187
latestPitcherVersion: pitcherManagerResponse.latestPitcherVersion,
187188
pitcherManagerVersion: pitcherManagerResponse.pitcherManagerVersion,

src/Sandboxes.ts

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -60,40 +60,6 @@ export class Sandboxes {
6060
);
6161
}
6262

63-
private async createTemplateSandbox(
64-
opts?: CreateSandboxOpts & StartSandboxOpts
65-
) {
66-
const templateId = opts?.id || this.defaultTemplateId;
67-
const privacy = opts?.privacy || "unlisted";
68-
const tags = opts?.tags || ["sdk"];
69-
let path = opts?.path || "/SDK";
70-
71-
if (!path.startsWith("/")) {
72-
path = "/" + path;
73-
}
74-
75-
// Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK.
76-
const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"];
77-
78-
const { mappedPrivacy, privatePreview } = mapPrivacyForApi(privacy);
79-
80-
const sandbox = await this.api.forkSandbox(templateId, {
81-
privacy: mappedPrivacy,
82-
title: opts?.title,
83-
description: opts?.description,
84-
tags: tagsWithSdk,
85-
path,
86-
private_preview: privatePreview,
87-
});
88-
89-
const startResponse = await this.api.startVm(
90-
sandbox.id,
91-
{ ...getStartOptions(opts), retryDelay: 200 } // Keep 200ms delay for creation
92-
);
93-
94-
return new Sandbox(sandbox.id, this.api, startResponse, this.tracer);
95-
}
96-
9763
/**
9864
* Resume a sandbox.
9965
*
@@ -129,6 +95,20 @@ export class Sandboxes {
12995
);
13096
}
13197

98+
/**
99+
* Permanently deletes a sandbox. This action is irreversible and will delete all data associated with the sandbox.
100+
* The sandbox must belong to your team's workspace to be deleted.
101+
*/
102+
async delete(sandboxId: string): Promise<void> {
103+
return this.withSpan(
104+
"sandboxes.delete",
105+
{ "sandbox.id": sandboxId },
106+
async () => {
107+
await this.api.deleteVm(sandboxId);
108+
}
109+
);
110+
}
111+
132112
/**
133113
* Forks a sandbox. This will create a new sandbox from the given sandbox.
134114
* @deprecated This will be removed shortly to avoid having multiple ways of doing the same thing
@@ -198,10 +178,38 @@ export class Sandboxes {
198178
"sandboxes.create",
199179
{
200180
"template.id": opts?.id || this.defaultTemplateId,
201-
"sandbox.privacy": opts?.privacy || "unlisted",
181+
"sandbox.privacy": opts?.privacy || "public-hosts",
202182
},
203183
async () => {
204-
return this.createTemplateSandbox(opts);
184+
const templateId = opts?.id || this.defaultTemplateId;
185+
const privacy = opts?.privacy || "public-hosts";
186+
const tags = opts?.tags || ["sdk"];
187+
let path = opts?.path || "/SDK";
188+
189+
if (!path.startsWith("/")) {
190+
path = "/" + path;
191+
}
192+
193+
// Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK.
194+
const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"];
195+
196+
const { mappedPrivacy, privatePreview } = mapPrivacyForApi(privacy);
197+
198+
const sandbox = await this.api.forkSandbox(templateId, {
199+
privacy: mappedPrivacy,
200+
title: opts?.title,
201+
description: opts?.description,
202+
tags: tagsWithSdk,
203+
path,
204+
private_preview: privatePreview,
205+
});
206+
207+
const startResponse = await this.api.startVm(
208+
sandbox.id,
209+
{ ...getStartOptions(opts), retryDelay: 200 } // Keep 200ms delay for creation
210+
);
211+
212+
return new Sandbox(sandbox.id, this.api, startResponse, this.tracer);
205213
}
206214
);
207215
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file is auto-generated by @hey-api/openapi-ts
22

3-
import { createClient, createConfig } from "@hey-api/client-fetch";
3+
import { createClient, createConfig } from '@hey-api/client-fetch';
44

5-
export const client = createClient(createConfig());
5+
export const client = createClient(createConfig());
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
// This file is auto-generated by @hey-api/openapi-ts
2-
export * from "./types.gen";
3-
export * from "./sdk.gen";
2+
export * from './types.gen';
3+
export * from './sdk.gen';
Lines changed: 20 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,29 @@
11
// This file is auto-generated by @hey-api/openapi-ts
22

3-
import type {
4-
Options as ClientOptions,
5-
TDataShape,
6-
Client,
7-
} from "@hey-api/client-fetch";
8-
import type {
9-
ContainerSetupData,
10-
ContainerSetupResponse,
11-
ContainerSetupError,
12-
} from "./types.gen";
13-
import { client as _heyApiClient } from "./client.gen";
3+
import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
4+
import type { ContainerSetupData, ContainerSetupResponse, ContainerSetupError } from './types.gen';
5+
import { client as _heyApiClient } from './client.gen';
146

15-
export type Options<
16-
TData extends TDataShape = TDataShape,
17-
ThrowOnError extends boolean = boolean
18-
> = ClientOptions<TData, ThrowOnError> & {
19-
/**
20-
* You can provide a client instance returned by `createClient()` instead of
21-
* individual options. This might be also useful if you want to implement a
22-
* custom client.
23-
*/
24-
client?: Client;
7+
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
8+
/**
9+
* You can provide a client instance returned by `createClient()` instead of
10+
* individual options. This might be also useful if you want to implement a
11+
* custom client.
12+
*/
13+
client?: Client;
2514
};
2615

2716
/**
2817
* Setup container
2918
* Set up a new container based on a template
3019
*/
31-
export const containerSetup = <ThrowOnError extends boolean = false>(
32-
options: Options<ContainerSetupData, ThrowOnError>
33-
) => {
34-
return (options.client ?? _heyApiClient).post<
35-
ContainerSetupResponse,
36-
ContainerSetupError,
37-
ThrowOnError
38-
>({
39-
url: "/container/setup",
40-
...options,
41-
headers: {
42-
"Content-Type": "application/json",
43-
...options?.headers,
44-
},
45-
});
46-
};
20+
export const containerSetup = <ThrowOnError extends boolean = false>(options: Options<ContainerSetupData, ThrowOnError>) => {
21+
return (options.client ?? _heyApiClient).post<ContainerSetupResponse, ContainerSetupError, ThrowOnError>({
22+
url: '/container/setup',
23+
...options,
24+
headers: {
25+
'Content-Type': 'application/json',
26+
...options?.headers
27+
}
28+
});
29+
};

0 commit comments

Comments
 (0)