Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ jobs:

- run: pnpm install --frozen-lockfile
- run: pnpm fmt:check
- run: pnpm type-check
- run: pnpm build
- run: pnpm type-check:all
- run: pnpm test:all
- run: pnpm build:all
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Changelog

## Unreleased

- Add the beta `@depot/sandbox` package under `packages/sandbox`.

See [GitHub Releases](https://github.com/depot/sdk-node/releases) for the latest changes.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ A Node.js SDK for the [Depot](https://depot.dev) API.

👉 [**API Documentation**](https://buf.build/depot/api)

This repository also contains the beta [`@depot/sandbox`](./packages/sandbox) package for the `depot.sandbox.v1` API.

## Installation

Use [pnpm](https://pnpm.io) or your favorite package manager:
Expand All @@ -16,6 +18,12 @@ Use [pnpm](https://pnpm.io) or your favorite package manager:
pnpm add @depot/sdk-node
```

After the sandbox beta is published, install it separately:

```bash
pnpm add @depot/sandbox@beta
```

## Usage

Each of the Depot API services is exposed on the main `depot` export. Authentication is provided via an `Authorization` header with an **Organization Token**, for each API request. The service paths match their corresponding gRPC service names.
Expand Down Expand Up @@ -43,6 +51,21 @@ async function example() {
}
```

### Sandbox Beta

```typescript
import {createClient, Sandbox} from '@depot/sandbox'

const client = createClient({token: process.env.DEPOT_TOKEN!})
const sandbox = await Sandbox.create(client)

const command = await sandbox.runCommand({cmd: 'echo', args: ['hello']})
await command.wait()

console.log(await command.stdout())
await sandbox.stop({blocking: true})
```

## License

MIT License, see `LICENSE`.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@
],
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
"build:all": "pnpm run build && pnpm --filter './packages/*' --if-present run build",
"clean": "rm -rf dist",
"fmt": "prettier --write .",
"fmt:check": "prettier --check .",
"generate": "buf generate buf.build/depot/api && prettier --write src/gen",
"prepack": "pnpm run build",
"type-check": "tsc --noEmit"
"test:all": "pnpm --filter './packages/*' --if-present run test",
"type-check": "tsc --noEmit",
"type-check:all": "pnpm run type-check && pnpm --filter './packages/*' --if-present run type-check"
},
"peerDependencies": {
"@bufbuild/protobuf": ">=2, <3"
Expand Down
67 changes: 67 additions & 0 deletions packages/sandbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# @depot/sandbox

Beta TypeScript SDK for Depot sandboxes.

This package wraps the `depot.sandbox.v1` API with Vercel-shaped classes for creating sandboxes, running commands, streaming command output, and using a `node:fs/promises`-shaped filesystem interface.

## Installation

After the beta package is published:

```bash
pnpm add @depot/sandbox@beta
```

## Usage

Set `DEPOT_TOKEN` in your environment, then create a client and pass it to the static sandbox entry points. Returned sandbox instances keep that client, so instance methods do not take a client argument.

```typescript
import {createClient, Sandbox} from '@depot/sandbox'

const client = createClient({token: process.env.DEPOT_TOKEN!})
const sandbox = await Sandbox.create(client, {
env: {NODE_ENV: 'development'},
})

const command = await sandbox.runCommand({cmd: 'echo', args: ['hello from depot']})
const finished = await command.wait()

console.log(finished.exitCode)
console.log(await command.stdout())

const fs = sandbox.fs()
await fs.writeFile('/tmp/message.txt', 'hello')
console.log(await fs.readFile('/tmp/message.txt', 'utf8'))

await sandbox.stop({blocking: true})
```

You can also pass organization and endpoint options explicitly:

```typescript
const client = createClient({
token: process.env.DEPOT_TOKEN!,
orgID: process.env.DEPOT_ORG_ID,
})
```

## Beta Surface

This beta package currently includes:

- `createClient`
- `Sandbox.create`, `Sandbox.get`, `Sandbox.list`, `Sandbox.listAll`
- `sandbox.stop`, `sandbox.kill`, `sandbox.runCommand`, `sandbox.fs`
- `SandboxCommandExecution.wait`, `logs`, `output`, `stdout`, and `stderr`
- `FileSystem` helpers for common file operations

Other sandbox capabilities, such as piped stdin, command history, create-time secrets, timeout extension, snapshots, and pty support, are not part of this beta surface yet.

## Generated Protos

The generated `depot.sandbox.v1` TypeScript files are vendored in `src/gen` for the beta package so customers do not need a separate published proto module.

## License

MIT License, see the repository `LICENSE`.
49 changes: 49 additions & 0 deletions packages/sandbox/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@depot/sandbox",
"version": "0.1.0-beta.0",
"type": "module",
"description": "TypeScript SDK for depot.sandbox.v1: create sandboxes, run commands, stream output, and use a node:fs/promises-shaped file system.",
"repository": {
"type": "git",
"url": "https://github.com/depot/sdk-node.git",
"directory": "packages/sandbox"
},
"license": "MIT",
"engines": {
"node": ">=20"
},
"main": "./dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc -p tsconfig.build.json",
"prepack": "pnpm run build",
"test": "tsx --test src/*.test.ts",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
"@bufbuild/protobuf": ">=2, <3"
},
"dependencies": {
"@connectrpc/connect": "^2.1.0",
"@connectrpc/connect-node": "^2.1.0"
},
"devDependencies": {
"@bufbuild/protobuf": "^2.8.0",
"@types/node": "^22.5.4",
"tsx": "^4.20.0",
"typescript": "^5.5.4"
},
"publishConfig": {
"access": "public",
"tag": "beta"
}
}
65 changes: 65 additions & 0 deletions packages/sandbox/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {createClient as createConnectClient, type Client} from '@connectrpc/connect'
import {createConnectTransport} from '@connectrpc/connect-node'
import {SandboxService} from './gen/depot/sandbox/v1/sandbox_pb.js'

/** Default Connect endpoint for the v1 sandbox API. */
export const DEFAULT_ENDPOINT = 'https://api.depot.dev'

/**
* Creates a `SandboxClient`. The returned value is an opaque handle that wraps
* the underlying Connect client; pass it to static Sandbox entry points.
*
* ```ts
* const client = createClient({token: process.env.DEPOT_TOKEN!})
* const sandbox = await Sandbox.create(client)
* ```
*/
export function createClient(opts: CreateClientOpts): SandboxClient {
if (!opts.token) {
throw new Error('createClient requires a token')
}
const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT
const transport = createConnectTransport({
baseUrl: endpoint,
httpVersion: '2',
interceptors: [
(next) => (req) => {
req.header.set('Authorization', `Bearer ${opts.token}`)
// App and service tokens, along with user tokens that belong to more
// than one organization, need the `x-depot-org` header so the server
// knows which organization to act on. Without it, those requests are
// rejected with PermissionDenied. A user token bound to a single
// organization works with or without the header.
if (opts.orgID) {
req.header.set('x-depot-org', opts.orgID)
}
return next(req)
},
],
})
return {
rpc: createConnectClient(SandboxService, transport),
endpoint,
}
}

export interface CreateClientOpts {
/** Bearer token used to authenticate requests, typically your `DEPOT_TOKEN`. */
token: string
/** API endpoint to connect to. Defaults to {@link DEFAULT_ENDPOINT}. */
endpoint?: string
/**
* Organization the client should act on. This is required for app and
* service tokens, and for user tokens that belong to more than one
* organization. For a user token bound to a single organization it is
* ignored, since that organization is already implied. This corresponds to
* the `--org` flag on the `depot` CLI.
*/
orgID?: string
}

/** Opaque client wrapping a Connect `Client<typeof SandboxService>`. */
export interface SandboxClient {
readonly rpc: Client<typeof SandboxService>
readonly endpoint: string
}
Loading
Loading