diff --git a/CHANGELOG.md b/CHANGELOG.md
index f48820a..8bed081 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,43 +2,39 @@
## [2.2.0](https://github.com/codesandbox/codesandbox-sdk/compare/v2.1.0...v2.2.0) (2025-09-02)
-
### Features
-* Add traceparent for all requests to API ([#180](https://github.com/codesandbox/codesandbox-sdk/issues/180)) ([b6f4846](https://github.com/codesandbox/codesandbox-sdk/commit/b6f484665de0bf0533127e098ff0ef1aa641a84b))
-* batch writes ([#175](https://github.com/codesandbox/codesandbox-sdk/issues/175)) ([493c5d5](https://github.com/codesandbox/codesandbox-sdk/commit/493c5d52d1eaa527d099b0270a5b0e78a694abbd))
-
+- Add traceparent for all requests to API ([#180](https://github.com/codesandbox/codesandbox-sdk/issues/180)) ([b6f4846](https://github.com/codesandbox/codesandbox-sdk/commit/b6f484665de0bf0533127e098ff0ef1aa641a84b))
+- batch writes ([#175](https://github.com/codesandbox/codesandbox-sdk/issues/175)) ([493c5d5](https://github.com/codesandbox/codesandbox-sdk/commit/493c5d52d1eaa527d099b0270a5b0e78a694abbd))
### Bug Fixes
-* ensure private preview on private sandbox ([#179](https://github.com/codesandbox/codesandbox-sdk/issues/179)) ([04381a0](https://github.com/codesandbox/codesandbox-sdk/commit/04381a071fb54aa385ad40ed7ff6d489945565ef))
-* prevent api config overrides ([#177](https://github.com/codesandbox/codesandbox-sdk/issues/177)) ([a9ec1a7](https://github.com/codesandbox/codesandbox-sdk/commit/a9ec1a78c2c83a53dfd0649dfa1764589b9fa671))
-* Queue messages on lost connection and reconnect also on hibernation ([#176](https://github.com/codesandbox/codesandbox-sdk/issues/176)) ([c5a8ffd](https://github.com/codesandbox/codesandbox-sdk/commit/c5a8ffdf4bcba321c4d3c9581f752c5e72a3bc8f))
+- ensure private preview on private sandbox ([#179](https://github.com/codesandbox/codesandbox-sdk/issues/179)) ([04381a0](https://github.com/codesandbox/codesandbox-sdk/commit/04381a071fb54aa385ad40ed7ff6d489945565ef))
+- prevent api config overrides ([#177](https://github.com/codesandbox/codesandbox-sdk/issues/177)) ([a9ec1a7](https://github.com/codesandbox/codesandbox-sdk/commit/a9ec1a78c2c83a53dfd0649dfa1764589b9fa671))
+- Queue messages on lost connection and reconnect also on hibernation ([#176](https://github.com/codesandbox/codesandbox-sdk/issues/176)) ([c5a8ffd](https://github.com/codesandbox/codesandbox-sdk/commit/c5a8ffdf4bcba321c4d3c9581f752c5e72a3bc8f))
## [2.1.0](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.7...v2.1.0) (2025-08-22)
-
### Features
-* add fetching single sandbox ([#142](https://github.com/codesandbox/codesandbox-sdk/issues/142)) ([2f58d43](https://github.com/codesandbox/codesandbox-sdk/commit/2f58d43ee44c98eeb7d0e917c8b423a80d202585))
-* add listRunning method to sandboxes namespace ([#145](https://github.com/codesandbox/codesandbox-sdk/issues/145)) ([6050dbd](https://github.com/codesandbox/codesandbox-sdk/commit/6050dbd289058c782e634a7ce9e25d9cf5921276))
-* add open telemetry for sandboxes methods ([#147](https://github.com/codesandbox/codesandbox-sdk/issues/147)) ([b331315](https://github.com/codesandbox/codesandbox-sdk/commit/b3313153b357dfda0d666a6a56ea79f6aa3dbbdf))
-* add tracing to Sandbox and SandboxClient, also allow passing to browser and node connectors ([#150](https://github.com/codesandbox/codesandbox-sdk/issues/150)) ([6ef2bf5](https://github.com/codesandbox/codesandbox-sdk/commit/6ef2bf51120068e35ff43607e3353a69d8fbf070))
-* Debug Sandboxes through CLI ([#163](https://github.com/codesandbox/codesandbox-sdk/issues/163)) ([9af1cdd](https://github.com/codesandbox/codesandbox-sdk/commit/9af1cdd0657c1a74572dc473ac6e04f6e1a40cd5))
-* enhance container setup logging in build command ([836a7a6](https://github.com/codesandbox/codesandbox-sdk/commit/836a7a6ed1dc7c73d737e04475083faf8d6d8fc4))
-* enhance container setup logging in build command ([a6f9fe7](https://github.com/codesandbox/codesandbox-sdk/commit/a6f9fe7c93450cfeded21048f62a6a2f0b842091))
-* private sandbox, public hosts with public-hosts privacy ([#154](https://github.com/codesandbox/codesandbox-sdk/issues/154)) ([dce7caf](https://github.com/codesandbox/codesandbox-sdk/commit/dce7cafd719e398b1f1421776bd55fa5601eccd0))
-
+- add fetching single sandbox ([#142](https://github.com/codesandbox/codesandbox-sdk/issues/142)) ([2f58d43](https://github.com/codesandbox/codesandbox-sdk/commit/2f58d43ee44c98eeb7d0e917c8b423a80d202585))
+- add listRunning method to sandboxes namespace ([#145](https://github.com/codesandbox/codesandbox-sdk/issues/145)) ([6050dbd](https://github.com/codesandbox/codesandbox-sdk/commit/6050dbd289058c782e634a7ce9e25d9cf5921276))
+- add open telemetry for sandboxes methods ([#147](https://github.com/codesandbox/codesandbox-sdk/issues/147)) ([b331315](https://github.com/codesandbox/codesandbox-sdk/commit/b3313153b357dfda0d666a6a56ea79f6aa3dbbdf))
+- add tracing to Sandbox and SandboxClient, also allow passing to browser and node connectors ([#150](https://github.com/codesandbox/codesandbox-sdk/issues/150)) ([6ef2bf5](https://github.com/codesandbox/codesandbox-sdk/commit/6ef2bf51120068e35ff43607e3353a69d8fbf070))
+- Debug Sandboxes through CLI ([#163](https://github.com/codesandbox/codesandbox-sdk/issues/163)) ([9af1cdd](https://github.com/codesandbox/codesandbox-sdk/commit/9af1cdd0657c1a74572dc473ac6e04f6e1a40cd5))
+- enhance container setup logging in build command ([836a7a6](https://github.com/codesandbox/codesandbox-sdk/commit/836a7a6ed1dc7c73d737e04475083faf8d6d8fc4))
+- enhance container setup logging in build command ([a6f9fe7](https://github.com/codesandbox/codesandbox-sdk/commit/a6f9fe7c93450cfeded21048f62a6a2f0b842091))
+- private sandbox, public hosts with public-hosts privacy ([#154](https://github.com/codesandbox/codesandbox-sdk/issues/154)) ([dce7caf](https://github.com/codesandbox/codesandbox-sdk/commit/dce7cafd719e398b1f1421776bd55fa5601eccd0))
### Bug Fixes
-* Add custom retry delay support for startVM API calls ([#156](https://github.com/codesandbox/codesandbox-sdk/issues/156)) ([ce3a282](https://github.com/codesandbox/codesandbox-sdk/commit/ce3a2823e66198f453d257a68757226d50e3bf17))
-* Decouple pitcher-client ([#148](https://github.com/codesandbox/codesandbox-sdk/issues/148)) ([3a6f9ea](https://github.com/codesandbox/codesandbox-sdk/commit/3a6f9ea213d978dc5a896bfc4d275deae6608abe))
-* friendly 503 error for overloaded Sandbox ([#172](https://github.com/codesandbox/codesandbox-sdk/issues/172)) ([f9987b1](https://github.com/codesandbox/codesandbox-sdk/commit/f9987b1a0b625bd580b94b6035c17d09d561cbbc))
-* include response handling in retries and dispose clients in build to avoid reconnecst ([#162](https://github.com/codesandbox/codesandbox-sdk/issues/162)) ([f70903a](https://github.com/codesandbox/codesandbox-sdk/commit/f70903a593c51bdfcdbf49d86b7b7bdce7cfe4a4))
-* properly dispose and prevent wakeups ([#170](https://github.com/codesandbox/codesandbox-sdk/issues/170)) ([029e3a5](https://github.com/codesandbox/codesandbox-sdk/commit/029e3a554010fe278eaeb6632f9df45264cbdc29))
-* Stabilize websocket connection ([#166](https://github.com/codesandbox/codesandbox-sdk/issues/166)) ([cb2f330](https://github.com/codesandbox/codesandbox-sdk/commit/cb2f330897c5e4c26637bc39a85d0e28dc4331d6))
-* update log line length to be smaller ([9a3099f](https://github.com/codesandbox/codesandbox-sdk/commit/9a3099fc3984c6ff01fa901ac1616cbc7b883119))
+- Add custom retry delay support for startVM API calls ([#156](https://github.com/codesandbox/codesandbox-sdk/issues/156)) ([ce3a282](https://github.com/codesandbox/codesandbox-sdk/commit/ce3a2823e66198f453d257a68757226d50e3bf17))
+- Decouple pitcher-client ([#148](https://github.com/codesandbox/codesandbox-sdk/issues/148)) ([3a6f9ea](https://github.com/codesandbox/codesandbox-sdk/commit/3a6f9ea213d978dc5a896bfc4d275deae6608abe))
+- friendly 503 error for overloaded Sandbox ([#172](https://github.com/codesandbox/codesandbox-sdk/issues/172)) ([f9987b1](https://github.com/codesandbox/codesandbox-sdk/commit/f9987b1a0b625bd580b94b6035c17d09d561cbbc))
+- include response handling in retries and dispose clients in build to avoid reconnecst ([#162](https://github.com/codesandbox/codesandbox-sdk/issues/162)) ([f70903a](https://github.com/codesandbox/codesandbox-sdk/commit/f70903a593c51bdfcdbf49d86b7b7bdce7cfe4a4))
+- properly dispose and prevent wakeups ([#170](https://github.com/codesandbox/codesandbox-sdk/issues/170)) ([029e3a5](https://github.com/codesandbox/codesandbox-sdk/commit/029e3a554010fe278eaeb6632f9df45264cbdc29))
+- Stabilize websocket connection ([#166](https://github.com/codesandbox/codesandbox-sdk/issues/166)) ([cb2f330](https://github.com/codesandbox/codesandbox-sdk/commit/cb2f330897c5e4c26637bc39a85d0e28dc4331d6))
+- update log line length to be smaller ([9a3099f](https://github.com/codesandbox/codesandbox-sdk/commit/9a3099fc3984c6ff01fa901ac1616cbc7b883119))
## [2.0.7](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.6...v2.0.7) (2025-08-06)
diff --git a/src/API.ts b/src/API.ts
index 8dcead5..544a2af 100644
--- a/src/API.ts
+++ b/src/API.ts
@@ -55,7 +55,6 @@ import type {
} from "./api-clients/client";
import { PitcherManagerResponse } from "./types";
-
export interface APIOptions {
apiKey: string;
config?: Config;
diff --git a/src/bin/ui/components/VmTable.tsx b/src/bin/ui/components/VmTable.tsx
index a9f626c..8e0054a 100644
--- a/src/bin/ui/components/VmTable.tsx
+++ b/src/bin/ui/components/VmTable.tsx
@@ -138,23 +138,24 @@ export const VmTable = ({
{/* Header */}
- {padString("VM ID", 14)} {padString("Last Active", 14)} {padString("Started At", 14)} {padString("Runtime", 10)} Credits
+ {padString("VM ID", 14)} {padString("Last Active", 14)}{" "}
+ {padString("Started At", 14)} {padString("Runtime", 10)} Credits
-
+
{/* Separator */}
{"─".repeat(Math.min(terminalWidth - 2, 60))}
-
+
{/* Data rows */}
{visibleVms.map((vm, visibleIndex) => {
const actualIndex = startIndex + visibleIndex;
const isSelected = selectedIndex === actualIndex;
-
+
// Safely get VM ID and handle edge cases
- const vmId = (vm?.id && typeof vm.id === 'string') ? vm.id : "N/A";
-
+ const vmId = vm?.id && typeof vm.id === "string" ? vm.id : "N/A";
+
// Skip rendering if VM is completely invalid
if (!vm) {
return (
@@ -163,26 +164,34 @@ export const VmTable = ({
);
}
-
+
return (
-
- {padString(vmId, 14)} {padString(formatDate(vm.last_active_at), 14)} {padString(formatDate(vm.session_started_at), 14)} {padString(calculateRuntime(vm.session_started_at, vm.last_active_at), 10)} {vm.credit_basis || "N/A"} cr/hr
+ {padString(vmId, 14)}{" "}
+ {padString(formatDate(vm.last_active_at), 14)}{" "}
+ {padString(formatDate(vm.session_started_at), 14)}{" "}
+ {padString(
+ calculateRuntime(vm.session_started_at, vm.last_active_at),
+ 10
+ )}{" "}
+ {vm.credit_basis || "N/A"} cr/hr
);
})}
-
+
{/* VM count and range info */}
{vmsSorted.length <= maxVisibleRows
? `${vmsSorted.length} VMs total`
- : `Showing ${startIndex + 1}-${endIndex} of ${vmsSorted.length} VMs`
- }
+ : `Showing ${startIndex + 1}-${endIndex} of ${
+ vmsSorted.length
+ } VMs`}
diff --git a/src/bin/ui/hooks/useVmInput.ts b/src/bin/ui/hooks/useVmInput.ts
index 4582f20..a7e591e 100644
--- a/src/bin/ui/hooks/useVmInput.ts
+++ b/src/bin/ui/hooks/useVmInput.ts
@@ -54,7 +54,7 @@ export const useVmInput = ({ vms, onSubmit }: UseVmInputOptions) => {
setSelectedVmIndex(newIndex);
const vm = vms[newIndex];
- const vmId = (vm?.id && typeof vm.id === 'string') ? vm.id : null;
+ const vmId = vm?.id && typeof vm.id === "string" ? vm.id : null;
setSelectedVm(vmId);
// Set the selected VM ID in the text input
diff --git a/src/bin/ui/views/Dashboard.tsx b/src/bin/ui/views/Dashboard.tsx
index 77ab059..2adc970 100644
--- a/src/bin/ui/views/Dashboard.tsx
+++ b/src/bin/ui/views/Dashboard.tsx
@@ -56,25 +56,27 @@ export const Dashboard = () => {
if (!a.session_started_at && !b.session_started_at) return 0;
if (!a.session_started_at) return 1;
if (!b.session_started_at) return -1;
-
+
const dateA = new Date(a.session_started_at);
const dateB = new Date(b.session_started_at);
-
+
// Check for invalid dates
if (isNaN(dateA.getTime()) && isNaN(dateB.getTime())) return 0;
if (isNaN(dateA.getTime())) return 1;
if (isNaN(dateB.getTime())) return -1;
-
+
return dateA.getTime() - dateB.getTime();
})
: [];
-
// Calculate visible rows dynamically based on terminal size
- // Account for UI elements: instructions (1), sandbox label (1), input field (1),
+ // Account for UI elements: instructions (1), sandbox label (1), input field (1),
// table title (1), table header (1), separator (1), bottom margin (2)
const uiElementRows = 8;
- const maxVisibleRows = Math.max(1, Math.floor((terminalHeight - uiElementRows) * 0.7) - 3);
+ const maxVisibleRows = Math.max(
+ 1,
+ Math.floor((terminalHeight - uiElementRows) * 0.7) - 3
+ );
const {
sandboxId,
diff --git a/src/bin/ui/views/Sandbox.tsx b/src/bin/ui/views/Sandbox.tsx
index a057d43..0c54865 100644
--- a/src/bin/ui/views/Sandbox.tsx
+++ b/src/bin/ui/views/Sandbox.tsx
@@ -3,6 +3,7 @@ import { Box, Text, useInput } from "ink";
import { useView } from "../viewContext";
import { useQuery } from "@tanstack/react-query";
import { useSDK } from "../sdkContext";
+import { exec } from "child_process";
export const Sandbox = () => {
const { view, setView } = useView<"sandbox">();
@@ -61,9 +62,16 @@ export const Sandbox = () => {
const getMenuOptions = () => {
switch (sandboxState) {
case "RUNNING":
- return ["Open", "Terminal", "Hibernate", "Shutdown", "Restart"];
+ return [
+ "Open",
+ "Open editor in browser",
+ "Terminal",
+ "Hibernate",
+ "Shutdown",
+ "Restart",
+ ];
case "IDLE":
- return ["Start"];
+ return ["Start", "Open editor in browser"];
default:
return [];
}
@@ -80,6 +88,24 @@ export const Sandbox = () => {
case "Open":
setView({ name: "open", params: { id: view.params.id } });
break;
+ case "Open editor in browser":
+ const url = `https://codesandbox.io/s/${view.params.id}`;
+ const platform = process.platform;
+
+ // Open browser based on platform
+ const command =
+ platform === "darwin"
+ ? `open "${url}"`
+ : platform === "win32"
+ ? `start "${url}"`
+ : `xdg-open "${url}"`; // Linux
+
+ exec(command, (error) => {
+ if (error) {
+ console.error(`Failed to open browser: ${error.message}`);
+ }
+ });
+ break;
case "Hibernate":
case "Shutdown":
setSandboxState("PENDING");
diff --git a/tests/sandbox-creation.test.ts b/tests/sandbox-creation.test.ts
index e93444b..ba9b2e6 100644
--- a/tests/sandbox-creation.test.ts
+++ b/tests/sandbox-creation.test.ts
@@ -1,80 +1,80 @@
-import { describe, it, expect, beforeEach, afterEach } from 'vitest'
-import nock from 'nock'
-import { CodeSandbox } from '../src/index'
-import {
- mockForkSandboxSuccess,
- mockStartVMSuccess,
- setupTestEnvironment,
- cleanupTestEnvironment
-} from './test-utils'
-
-describe('Sandbox Creation', () => {
+import { describe, it, expect, beforeEach, afterEach } from "vitest";
+import nock from "nock";
+import { CodeSandbox } from "../src/index";
+import {
+ mockForkSandboxSuccess,
+ mockStartVMSuccess,
+ setupTestEnvironment,
+ cleanupTestEnvironment,
+} from "./test-utils";
+
+describe("Sandbox Creation", () => {
beforeEach(() => {
- setupTestEnvironment()
- })
+ setupTestEnvironment();
+ });
afterEach(() => {
- cleanupTestEnvironment()
- })
+ cleanupTestEnvironment();
+ });
- it('should successfully create and start a sandbox', async () => {
+ it("should successfully create and start a sandbox", async () => {
// Mock the fork sandbox API call (pcz35m is the default template)
- const forkScope = mockForkSandboxSuccess('test-sandbox-123', {
- title: 'Test Sandbox',
- description: 'Integration test sandbox',
+ const forkScope = mockForkSandboxSuccess("test-sandbox-123", {
+ title: "Test Sandbox",
+ description: "Integration test sandbox",
privacy: 1,
- tags: ['integration-test', 'sdk']
- })
+ tags: ["integration-test", "sdk"],
+ });
// Mock the start VM API call - use regex to match any ID
- const startScope = mockStartVMSuccess('test-sandbox-123')
+ const startScope = mockStartVMSuccess("test-sandbox-123");
// Initialize SDK
- const sdk = new CodeSandbox()
-
+ const sdk = new CodeSandbox();
+
// Create sandbox
const sandbox = await sdk.sandboxes.create({
- title: 'Test Sandbox',
- description: 'Integration test sandbox',
- privacy: 'unlisted',
- tags: ['integration-test']
- })
+ title: "Test Sandbox",
+ description: "Integration test sandbox",
+ privacy: "unlisted",
+ tags: ["integration-test"],
+ });
// Verify sandbox was created successfully
- expect(sandbox).toBeDefined()
- expect(sandbox.id).toBe('test-sandbox-123')
-
+ expect(sandbox).toBeDefined();
+ expect(sandbox.id).toBe("test-sandbox-123");
+
// Verify all API calls were made
- expect(forkScope.isDone()).toBe(true)
- expect(startScope.isDone()).toBe(true)
- }, 10000) // 10 second timeout for integration test
+ expect(forkScope.isDone()).toBe(true);
+ expect(startScope.isDone()).toBe(true);
+ }, 10000); // 10 second timeout for integration test
- it('should use default template when no id is provided', async () => {
+ it("should use default template when no id is provided", async () => {
// Mock default template call - pcz35m is the default template
- const forkScope = mockForkSandboxSuccess('default-sandbox-456')
+ const forkScope = mockForkSandboxSuccess("default-sandbox-456");
- const startScope = mockStartVMSuccess('default-sandbox-456')
+ const startScope = mockStartVMSuccess("default-sandbox-456");
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Create sandbox without specifying template id
- const sandbox = await sdk.sandboxes.create()
+ const sandbox = await sdk.sandboxes.create();
- expect(sandbox).toBeDefined()
- expect(sandbox.id).toBe('default-sandbox-456')
- expect(forkScope.isDone()).toBe(true)
- expect(startScope.isDone()).toBe(true)
- })
+ expect(sandbox).toBeDefined();
+ expect(sandbox.id).toBe("default-sandbox-456");
+ expect(forkScope.isDone()).toBe(true);
+ expect(startScope.isDone()).toBe(true);
+ });
- it('should handle API errors gracefully', async () => {
+ it("should handle API errors gracefully", async () => {
// Mock fork sandbox failure
- nock('https://api.codesandbox.io')
- .post('/sandbox/pcz35m/fork')
- .reply(500, { message: 'Internal server error' })
+ nock("https://api.codesandbox.io")
+ .post("/sandbox/pcz35m/fork")
+ .reply(500, { message: "Internal server error" });
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Expect the creation to throw an error
- await expect(sdk.sandboxes.create()).rejects.toThrow()
- })
-})
\ No newline at end of file
+ await expect(sdk.sandboxes.create()).rejects.toThrow();
+ });
+});
diff --git a/tests/sandbox-lifecycle.test.ts b/tests/sandbox-lifecycle.test.ts
index 41d4fa1..7967bd5 100644
--- a/tests/sandbox-lifecycle.test.ts
+++ b/tests/sandbox-lifecycle.test.ts
@@ -1,135 +1,139 @@
-import { describe, it, expect, beforeEach, afterEach } from 'vitest'
-import { CodeSandbox } from '../src/index'
-import {
+import { describe, it, expect, beforeEach, afterEach } from "vitest";
+import { CodeSandbox } from "../src/index";
+import {
mockHibernateSuccess,
mockHibernateFailure,
mockStartVMSuccess,
mockShutdownSuccess,
mockShutdownFailure,
mockStartVMFailure,
- setupTestEnvironment,
- cleanupTestEnvironment
-} from './test-utils'
+ setupTestEnvironment,
+ cleanupTestEnvironment,
+} from "./test-utils";
-describe('Sandbox lifecycle operations', () => {
+describe("Sandbox lifecycle operations", () => {
beforeEach(() => {
- setupTestEnvironment()
- })
+ setupTestEnvironment();
+ });
afterEach(() => {
- cleanupTestEnvironment()
- })
+ cleanupTestEnvironment();
+ });
+
+ it("should successfully hibernate a sandbox", async () => {
+ const sandboxId = "test-sandbox-hibernate";
- it('should successfully hibernate a sandbox', async () => {
- const sandboxId = 'test-sandbox-hibernate'
-
// Mock hibernate API call
- const hibernateScope = mockHibernateSuccess(sandboxId)
+ const hibernateScope = mockHibernateSuccess(sandboxId);
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Test hibernate operation
- await expect(sdk.sandboxes.hibernate(sandboxId)).resolves.not.toThrow()
-
+ await expect(sdk.sandboxes.hibernate(sandboxId)).resolves.not.toThrow();
+
// Verify API call was made
- expect(hibernateScope.isDone()).toBe(true)
- })
+ expect(hibernateScope.isDone()).toBe(true);
+ });
+
+ it("should successfully resume a sandbox", async () => {
+ const sandboxId = "test-sandbox-resume";
- it('should successfully resume a sandbox', async () => {
- const sandboxId = 'test-sandbox-resume'
-
// Mock resume (startVm) API call
- const resumeScope = mockStartVMSuccess(sandboxId, 'RESUME')
+ const resumeScope = mockStartVMSuccess(sandboxId, "RESUME");
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Test resume operation
- const sandbox = await sdk.sandboxes.resume(sandboxId)
-
+ const sandbox = await sdk.sandboxes.resume(sandboxId);
+
// Verify sandbox was resumed successfully
- expect(sandbox).toBeDefined()
- expect(sandbox.id).toBe(sandboxId)
- expect(sandbox.bootupType).toBe('RESUME')
- expect(resumeScope.isDone()).toBe(true)
- })
-
- it('should successfully restart a sandbox', async () => {
- const sandboxId = 'test-sandbox-restart'
-
+ expect(sandbox).toBeDefined();
+ expect(sandbox.id).toBe(sandboxId);
+ expect(sandbox.bootupType).toBe("RESUME");
+ expect(resumeScope.isDone()).toBe(true);
+ });
+
+ it("should successfully restart a sandbox", async () => {
+ const sandboxId = "test-sandbox-restart";
+
// Mock shutdown API call
- const shutdownScope = mockShutdownSuccess(sandboxId)
+ const shutdownScope = mockShutdownSuccess(sandboxId);
// Mock start VM API call (after shutdown)
- const startScope = mockStartVMSuccess(sandboxId, 'CLEAN')
+ const startScope = mockStartVMSuccess(sandboxId, "CLEAN");
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Test restart operation
- const sandbox = await sdk.sandboxes.restart(sandboxId)
-
+ const sandbox = await sdk.sandboxes.restart(sandboxId);
+
// Verify sandbox was restarted successfully
- expect(sandbox).toBeDefined()
- expect(sandbox.id).toBe(sandboxId)
- expect(sandbox.bootupType).toBe('CLEAN')
- expect(shutdownScope.isDone()).toBe(true)
- expect(startScope.isDone()).toBe(true)
- })
-
- it('should retry API calls on failure and eventually succeed', async () => {
- const sandboxId = 'test-sandbox-retry-success'
-
+ expect(sandbox).toBeDefined();
+ expect(sandbox.id).toBe(sandboxId);
+ expect(sandbox.bootupType).toBe("CLEAN");
+ expect(shutdownScope.isDone()).toBe(true);
+ expect(startScope.isDone()).toBe(true);
+ });
+
+ it("should retry API calls on failure and eventually succeed", async () => {
+ const sandboxId = "test-sandbox-retry-success";
+
// Mock hibernate API to fail twice, then succeed on 3rd attempt
- mockHibernateFailure(sandboxId, 2, 'Server error')
- const hibernateScope = mockHibernateSuccess(sandboxId)
+ mockHibernateFailure(sandboxId, 2, "Server error");
+ const hibernateScope = mockHibernateSuccess(sandboxId);
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Test should succeed after retries
- await expect(sdk.sandboxes.hibernate(sandboxId)).resolves.not.toThrow()
-
+ await expect(sdk.sandboxes.hibernate(sandboxId)).resolves.not.toThrow();
+
// Verify all retry attempts were made
- expect(hibernateScope.isDone()).toBe(true)
- }, 10000) // Longer timeout for retry test
+ expect(hibernateScope.isDone()).toBe(true);
+ }, 10000); // Longer timeout for retry test
+
+ it("should fail after exhausting all retry attempts", async () => {
+ const sandboxId = "test-sandbox-retry-fail";
- it('should fail after exhausting all retry attempts', async () => {
- const sandboxId = 'test-sandbox-retry-fail'
-
// Mock hibernate API to fail 3 times (exhaust retries)
- mockHibernateFailure(sandboxId, 3, 'Persistent server error')
+ mockHibernateFailure(sandboxId, 3, "Persistent server error");
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Test should fail after all retries are exhausted
- await expect(sdk.sandboxes.hibernate(sandboxId)).rejects.toThrow()
- }, 10000) // Longer timeout for retry test
+ await expect(sdk.sandboxes.hibernate(sandboxId)).rejects.toThrow();
+ }, 10000); // Longer timeout for retry test
+
+ it("should handle restart failure during shutdown phase", async () => {
+ const sandboxId = "test-sandbox-restart-fail";
- it('should handle restart failure during shutdown phase', async () => {
- const sandboxId = 'test-sandbox-restart-fail'
-
// Mock shutdown to fail
- mockShutdownFailure(sandboxId, 3, 'Shutdown failed')
+ mockShutdownFailure(sandboxId, 3, "Shutdown failed");
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Test should fail during shutdown phase
- await expect(sdk.sandboxes.restart(sandboxId)).rejects.toThrow('Failed to shutdown VM')
- })
+ await expect(sdk.sandboxes.restart(sandboxId)).rejects.toThrow(
+ "Failed to shutdown VM"
+ );
+ });
+
+ it("should handle restart failure during start phase", async () => {
+ const sandboxId = "test-sandbox-restart-start-fail";
- it('should handle restart failure during start phase', async () => {
- const sandboxId = 'test-sandbox-restart-start-fail'
-
// Mock successful shutdown
- const shutdownScope = mockShutdownSuccess(sandboxId)
+ const shutdownScope = mockShutdownSuccess(sandboxId);
// Mock start VM to fail
- mockStartVMFailure(3, 'Start failed')
+ mockStartVMFailure(3, "Start failed");
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Test should fail during start phase
- await expect(sdk.sandboxes.restart(sandboxId)).rejects.toThrow('Failed to start VM')
-
+ await expect(sdk.sandboxes.restart(sandboxId)).rejects.toThrow(
+ "Failed to start VM"
+ );
+
// Verify both phases were attempted
- expect(shutdownScope.isDone()).toBe(true)
- })
-})
\ No newline at end of file
+ expect(shutdownScope.isDone()).toBe(true);
+ });
+});
diff --git a/tests/sandbox-retry-behavior.test.ts b/tests/sandbox-retry-behavior.test.ts
index 4e03c80..e09acbe 100644
--- a/tests/sandbox-retry-behavior.test.ts
+++ b/tests/sandbox-retry-behavior.test.ts
@@ -1,168 +1,168 @@
-import { describe, it, expect, beforeEach, afterEach } from 'vitest'
-import nock from 'nock'
-import { CodeSandbox } from '../src/index'
-import {
- mockForkSandboxSuccess,
- mockStartVMSuccess,
- mockStartVMFailure,
- setupTestEnvironment,
- cleanupTestEnvironment
-} from './test-utils'
-
-describe('Create operation retry behavior', () => {
+import { describe, it, expect, beforeEach, afterEach } from "vitest";
+import nock from "nock";
+import { CodeSandbox } from "../src/index";
+import {
+ mockForkSandboxSuccess,
+ mockStartVMSuccess,
+ mockStartVMFailure,
+ setupTestEnvironment,
+ cleanupTestEnvironment,
+} from "./test-utils";
+
+describe("Create operation retry behavior", () => {
beforeEach(() => {
- setupTestEnvironment()
- })
+ setupTestEnvironment();
+ });
afterEach(() => {
- cleanupTestEnvironment()
- })
+ cleanupTestEnvironment();
+ });
+
+ it("should fail immediately on fork API error (no retry for fork)", async () => {
+ let forkRequestCount = 0;
- it('should fail immediately on fork API error (no retry for fork)', async () => {
- let forkRequestCount = 0
-
// Mock fork to fail once - should fail immediately since fork doesn't retry
- const forkScope = nock('https://api.codesandbox.io')
- .post('/sandbox/pcz35m/fork')
+ const forkScope = nock("https://api.codesandbox.io")
+ .post("/sandbox/pcz35m/fork")
.reply(500, () => {
- forkRequestCount++
- return { error: { errors: ['Fork failed'] } }
- })
+ forkRequestCount++;
+ return { error: { errors: ["Fork failed"] } };
+ });
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Should fail immediately without retries
- const startTime = Date.now()
- await expect(sdk.sandboxes.create()).rejects.toThrow()
- const duration = Date.now() - startTime
-
+ const startTime = Date.now();
+ await expect(sdk.sandboxes.create()).rejects.toThrow();
+ const duration = Date.now() - startTime;
+
// Should fail quickly (no retry delays)
- expect(duration).toBeLessThan(1000)
- expect(forkScope.isDone()).toBe(true)
- expect(forkRequestCount).toBe(1) // Should only make 1 request (no retries)
- })
-
- it('should retry start VM failures and eventually succeed', async () => {
- let startVMRequestCount = 0
-
+ expect(duration).toBeLessThan(1000);
+ expect(forkScope.isDone()).toBe(true);
+ expect(forkRequestCount).toBe(1); // Should only make 1 request (no retries)
+ });
+
+ it("should retry start VM failures and eventually succeed", async () => {
+ let startVMRequestCount = 0;
+
// Mock successful fork
- const forkScope = mockForkSandboxSuccess('test-sandbox-start-retry')
+ const forkScope = mockForkSandboxSuccess("test-sandbox-start-retry");
// Mock start VM to fail twice
- const failureScope = nock('https://api.codesandbox.io')
+ const failureScope = nock("https://api.codesandbox.io")
.post(/\/vm\/.*\/start/)
.times(2)
.reply(500, () => {
- startVMRequestCount++
- return { error: { errors: ['Start failed'] } }
- })
-
+ startVMRequestCount++;
+ return { error: { errors: ["Start failed"] } };
+ });
+
// Then succeed on 3rd attempt
- const startScope = nock('https://api.codesandbox.io')
+ const startScope = nock("https://api.codesandbox.io")
.post(/\/vm\/.*\/start/)
.reply(200, () => {
- startVMRequestCount++
+ startVMRequestCount++;
return {
data: {
- bootup_type: 'CLEAN',
- cluster: 'test-cluster',
+ bootup_type: "CLEAN",
+ cluster: "test-cluster",
pitcher_url: `wss://pitcher.codesandbox.io/test-sandbox-start-retry`,
- workspace_path: '/project/sandbox',
- user_workspace_path: '/project/sandbox',
- pitcher_manager_version: '1.0.0',
- pitcher_version: '1.0.0',
- latest_pitcher_version: '1.0.0',
- pitcher_token: `pitcher-token-retry`
- }
- }
- })
-
- const sdk = new CodeSandbox()
-
+ workspace_path: "/project/sandbox",
+ user_workspace_path: "/project/sandbox",
+ pitcher_manager_version: "1.0.0",
+ pitcher_version: "1.0.0",
+ latest_pitcher_version: "1.0.0",
+ pitcher_token: `pitcher-token-retry`,
+ },
+ };
+ });
+
+ const sdk = new CodeSandbox();
+
// Should succeed after start VM retries
- const sandbox = await sdk.sandboxes.create()
-
- expect(sandbox).toBeDefined()
- expect(sandbox.id).toBe('test-sandbox-start-retry')
- expect(forkScope.isDone()).toBe(true)
- expect(failureScope.isDone()).toBe(true)
- expect(startScope.isDone()).toBe(true)
- expect(startVMRequestCount).toBe(3) // Should make 3 requests (2 failures + 1 success)
- }, 10000) // Longer timeout for retries
-
- it('should fail create after start VM exhausts all retries', async () => {
- let startVMRequestCount = 0
-
+ const sandbox = await sdk.sandboxes.create();
+
+ expect(sandbox).toBeDefined();
+ expect(sandbox.id).toBe("test-sandbox-start-retry");
+ expect(forkScope.isDone()).toBe(true);
+ expect(failureScope.isDone()).toBe(true);
+ expect(startScope.isDone()).toBe(true);
+ expect(startVMRequestCount).toBe(3); // Should make 3 requests (2 failures + 1 success)
+ }, 10000); // Longer timeout for retries
+
+ it("should fail create after start VM exhausts all retries", async () => {
+ let startVMRequestCount = 0;
+
// Mock successful fork
- const forkScope = mockForkSandboxSuccess('test-sandbox-start-fail')
+ const forkScope = mockForkSandboxSuccess("test-sandbox-start-fail");
// Mock start VM to fail all 3 retry attempts
- const failureScope = nock('https://api.codesandbox.io')
+ const failureScope = nock("https://api.codesandbox.io")
.post(/\/vm\/.*\/start/)
.times(3)
.reply(500, () => {
- startVMRequestCount++
- return { error: { errors: ['Persistent start failure'] } }
- })
+ startVMRequestCount++;
+ return { error: { errors: ["Persistent start failure"] } };
+ });
+
+ const sdk = new CodeSandbox();
- const sdk = new CodeSandbox()
-
// Should fail after exhausting start VM retries
- await expect(sdk.sandboxes.create()).rejects.toThrow()
-
- expect(forkScope.isDone()).toBe(true)
- expect(failureScope.isDone()).toBe(true)
- expect(startVMRequestCount).toBe(3) // Should make exactly 3 retry attempts
- }, 10000) // Longer timeout for retries
-
- it('should validate retry timing for start VM failures', async () => {
- let startVMRequestCount = 0
-
+ await expect(sdk.sandboxes.create()).rejects.toThrow();
+
+ expect(forkScope.isDone()).toBe(true);
+ expect(failureScope.isDone()).toBe(true);
+ expect(startVMRequestCount).toBe(3); // Should make exactly 3 retry attempts
+ }, 10000); // Longer timeout for retries
+
+ it("should validate retry timing for start VM failures", async () => {
+ let startVMRequestCount = 0;
+
// Mock successful fork
- const forkScope = mockForkSandboxSuccess('test-sandbox-timing')
+ const forkScope = mockForkSandboxSuccess("test-sandbox-timing");
// Mock start VM to fail twice
- const failureScope = nock('https://api.codesandbox.io')
+ const failureScope = nock("https://api.codesandbox.io")
.post(/\/vm\/.*\/start/)
.times(2)
.reply(500, () => {
- startVMRequestCount++
- return { error: { errors: ['Start failed'] } }
- })
-
+ startVMRequestCount++;
+ return { error: { errors: ["Start failed"] } };
+ });
+
// Then succeed on 3rd attempt
- const startScope = nock('https://api.codesandbox.io')
+ const startScope = nock("https://api.codesandbox.io")
.post(/\/vm\/.*\/start/)
.reply(200, () => {
- startVMRequestCount++
+ startVMRequestCount++;
return {
data: {
- bootup_type: 'CLEAN',
- cluster: 'test-cluster',
+ bootup_type: "CLEAN",
+ cluster: "test-cluster",
pitcher_url: `wss://pitcher.codesandbox.io/test-sandbox-timing`,
- workspace_path: '/project/sandbox',
- user_workspace_path: '/project/sandbox',
- pitcher_manager_version: '1.0.0',
- pitcher_version: '1.0.0',
- latest_pitcher_version: '1.0.0',
- pitcher_token: `pitcher-token-timing`
- }
- }
- })
-
- const sdk = new CodeSandbox()
-
- const startTime = Date.now()
- const sandbox = await sdk.sandboxes.create()
- const duration = Date.now() - startTime
-
+ workspace_path: "/project/sandbox",
+ user_workspace_path: "/project/sandbox",
+ pitcher_manager_version: "1.0.0",
+ pitcher_version: "1.0.0",
+ latest_pitcher_version: "1.0.0",
+ pitcher_token: `pitcher-token-timing`,
+ },
+ };
+ });
+
+ const sdk = new CodeSandbox();
+
+ const startTime = Date.now();
+ const sandbox = await sdk.sandboxes.create();
+ const duration = Date.now() - startTime;
+
// Should take at least 400ms (2 retries × 200ms delay each)
- expect(duration).toBeGreaterThanOrEqual(300) // Allow some tolerance
- expect(sandbox).toBeDefined()
- expect(sandbox.id).toBe('test-sandbox-timing')
- expect(forkScope.isDone()).toBe(true)
- expect(failureScope.isDone()).toBe(true)
- expect(startScope.isDone()).toBe(true)
- expect(startVMRequestCount).toBe(3) // Should make 3 requests (2 failures + 1 success)
- }, 10000) // Longer timeout for retries
-})
\ No newline at end of file
+ expect(duration).toBeGreaterThanOrEqual(300); // Allow some tolerance
+ expect(sandbox).toBeDefined();
+ expect(sandbox.id).toBe("test-sandbox-timing");
+ expect(forkScope.isDone()).toBe(true);
+ expect(failureScope.isDone()).toBe(true);
+ expect(startScope.isDone()).toBe(true);
+ expect(startVMRequestCount).toBe(3); // Should make 3 requests (2 failures + 1 success)
+ }, 10000); // Longer timeout for retries
+});
diff --git a/tests/test-utils.ts b/tests/test-utils.ts
index 6ad1cf8..26430a6 100644
--- a/tests/test-utils.ts
+++ b/tests/test-utils.ts
@@ -1,99 +1,116 @@
-import nock from 'nock'
+import nock from "nock";
-export const mockForkSandboxSuccess = (sandboxId: string, options?: {
- title?: string
- description?: string
- privacy?: number
- tags?: string[]
-}) => {
- return nock('https://api.codesandbox.io')
- .post('/sandbox/pcz35m/fork', {
+export const mockForkSandboxSuccess = (
+ sandboxId: string,
+ options?: {
+ title?: string;
+ description?: string;
+ privacy?: number;
+ tags?: string[];
+ }
+) => {
+ return nock("https://api.codesandbox.io")
+ .post("/sandbox/pcz35m/fork", {
privacy: options?.privacy ?? 1,
...(options?.title && { title: options.title }),
...(options?.description && { description: options.description }),
- tags: options?.tags ?? ['sdk'],
- path: '/SDK'
+ tags: options?.tags ?? ["sdk"],
+ path: "/SDK",
})
.reply(200, {
data: {
id: sandboxId,
- title: options?.title ?? 'Test Sandbox',
+ title: options?.title ?? "Test Sandbox",
description: options?.description,
privacy: options?.privacy ?? 1,
- tags: options?.tags ?? ['sdk'],
- created_at: '2025-01-21T12:00:00Z',
- updated_at: '2025-01-21T12:00:00Z'
- }
- })
-}
+ tags: options?.tags ?? ["sdk"],
+ created_at: "2025-01-21T12:00:00Z",
+ updated_at: "2025-01-21T12:00:00Z",
+ },
+ });
+};
-export const mockStartVMSuccess = (sandboxId: string, bootupType: 'CLEAN' | 'RESUME' = 'CLEAN') => {
- return nock('https://api.codesandbox.io')
+export const mockStartVMSuccess = (
+ sandboxId: string,
+ bootupType: "CLEAN" | "RESUME" = "CLEAN"
+) => {
+ return nock("https://api.codesandbox.io")
.post(/\/vm\/.*\/start/)
.reply(200, {
data: {
bootup_type: bootupType,
- cluster: 'test-cluster',
+ cluster: "test-cluster",
pitcher_url: `wss://pitcher.codesandbox.io/${sandboxId}`,
- workspace_path: '/project/sandbox',
- user_workspace_path: '/project/sandbox',
- pitcher_manager_version: '1.0.0',
- pitcher_version: '1.0.0',
- latest_pitcher_version: '1.0.0',
- pitcher_token: `pitcher-token-${sandboxId.split('-').pop()}`
- }
- })
-}
+ workspace_path: "/project/sandbox",
+ user_workspace_path: "/project/sandbox",
+ pitcher_manager_version: "1.0.0",
+ pitcher_version: "1.0.0",
+ latest_pitcher_version: "1.0.0",
+ pitcher_token: `pitcher-token-${sandboxId.split("-").pop()}`,
+ },
+ });
+};
-export const mockStartVMFailure = (times: number = 1, errorMessage: string = 'Start failed') => {
- return nock('https://api.codesandbox.io')
+export const mockStartVMFailure = (
+ times: number = 1,
+ errorMessage: string = "Start failed"
+) => {
+ return nock("https://api.codesandbox.io")
.post(/\/vm\/.*\/start/)
.times(times)
- .reply(500, { error: { errors: [errorMessage] } })
-}
+ .reply(500, { error: { errors: [errorMessage] } });
+};
export const mockHibernateSuccess = (sandboxId: string) => {
- return nock('https://api.codesandbox.io')
+ return nock("https://api.codesandbox.io")
.post(`/vm/${sandboxId}/hibernate`)
.reply(200, {
data: {
- success: true
- }
- })
-}
+ success: true,
+ },
+ });
+};
-export const mockHibernateFailure = (sandboxId: string, times: number = 1, errorMessage: string = 'Server error') => {
- return nock('https://api.codesandbox.io')
+export const mockHibernateFailure = (
+ sandboxId: string,
+ times: number = 1,
+ errorMessage: string = "Server error"
+) => {
+ return nock("https://api.codesandbox.io")
.post(`/vm/${sandboxId}/hibernate`)
.times(times)
- .reply(500, { error: { errors: [errorMessage] } })
-}
+ .reply(500, { error: { errors: [errorMessage] } });
+};
export const mockShutdownSuccess = (sandboxId: string) => {
- return nock('https://api.codesandbox.io')
+ return nock("https://api.codesandbox.io")
.post(`/vm/${sandboxId}/shutdown`)
.reply(200, {
data: {
- success: true
- }
- })
-}
+ success: true,
+ },
+ });
+};
-export const mockShutdownFailure = (sandboxId: string, times: number = 1, errorMessage: string = 'Shutdown failed') => {
- return nock('https://api.codesandbox.io')
+export const mockShutdownFailure = (
+ sandboxId: string,
+ times: number = 1,
+ errorMessage: string = "Shutdown failed"
+) => {
+ return nock("https://api.codesandbox.io")
.post(`/vm/${sandboxId}/shutdown`)
.times(times)
- .reply(500, { error: { errors: [errorMessage] } })
-}
+ .reply(500, { error: { errors: [errorMessage] } });
+};
export const setupTestEnvironment = () => {
- process.env.CSB_API_KEY = 'csb_test_key_123'
- nock.cleanAll()
-}
+ process.env.CSB_API_KEY = "csb_test_key_123";
+ nock.cleanAll();
+};
export const cleanupTestEnvironment = () => {
if (!nock.isDone()) {
- console.error('Unused nock interceptors:', nock.pendingMocks())
+ console.error("Unused nock interceptors:", nock.pendingMocks());
}
- nock.cleanAll()
-}
\ No newline at end of file
+ nock.cleanAll();
+};
diff --git a/vitest.config.ts b/vitest.config.ts
index cba2eed..e941089 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -1,11 +1,11 @@
-import { defineConfig } from 'vitest/config'
+import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
- environment: 'node',
- include: ['tests/**/*.test.ts'],
+ environment: "node",
+ include: ["tests/**/*.test.ts"],
},
define: {
- CSB_SDK_VERSION: JSON.stringify('2.1.0-rc.4'),
+ CSB_SDK_VERSION: JSON.stringify("2.1.0-rc.4"),
},
-})
\ No newline at end of file
+});