From 5e87e1db8fae2f74b0f37ce76e19ae3c09ae144a Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 9 Jun 2026 13:28:15 -0700 Subject: [PATCH] Revert "feat: add @depot/sandbox package" --- .github/workflows/ci.yml | 5 +- CHANGELOG.md | 4 - README.md | 23 - package.json | 5 +- packages/sandbox/README.md | 67 -- packages/sandbox/package.json | 49 - packages/sandbox/src/client.ts | 65 - packages/sandbox/src/command.test.ts | 280 ----- packages/sandbox/src/command.ts | 561 --------- packages/sandbox/src/errors.ts | 142 --- packages/sandbox/src/filesystem.test.ts | 356 ------ packages/sandbox/src/filesystem.ts | 528 -------- .../src/gen/depot/sandbox/v1/command_pb.ts | 523 -------- .../src/gen/depot/sandbox/v1/filesystem_pb.ts | 1059 ----------------- .../src/gen/depot/sandbox/v1/refs_pb.ts | 46 - .../src/gen/depot/sandbox/v1/sandbox_pb.ts | 893 -------------- packages/sandbox/src/index.ts | 44 - packages/sandbox/src/k-streaming.test.ts | 66 - packages/sandbox/src/k-streaming.ts | 278 ----- packages/sandbox/src/sandbox.test.ts | 281 ----- packages/sandbox/src/sandbox.ts | 608 ---------- packages/sandbox/src/types.ts | 99 -- packages/sandbox/tsconfig.build.json | 4 - packages/sandbox/tsconfig.json | 24 - pnpm-lock.yaml | 311 +---- pnpm-workspace.yaml | 3 - 26 files changed, 7 insertions(+), 6317 deletions(-) delete mode 100644 packages/sandbox/README.md delete mode 100644 packages/sandbox/package.json delete mode 100644 packages/sandbox/src/client.ts delete mode 100644 packages/sandbox/src/command.test.ts delete mode 100644 packages/sandbox/src/command.ts delete mode 100644 packages/sandbox/src/errors.ts delete mode 100644 packages/sandbox/src/filesystem.test.ts delete mode 100644 packages/sandbox/src/filesystem.ts delete mode 100644 packages/sandbox/src/gen/depot/sandbox/v1/command_pb.ts delete mode 100644 packages/sandbox/src/gen/depot/sandbox/v1/filesystem_pb.ts delete mode 100644 packages/sandbox/src/gen/depot/sandbox/v1/refs_pb.ts delete mode 100644 packages/sandbox/src/gen/depot/sandbox/v1/sandbox_pb.ts delete mode 100644 packages/sandbox/src/index.ts delete mode 100644 packages/sandbox/src/k-streaming.test.ts delete mode 100644 packages/sandbox/src/k-streaming.ts delete mode 100644 packages/sandbox/src/sandbox.test.ts delete mode 100644 packages/sandbox/src/sandbox.ts delete mode 100644 packages/sandbox/src/types.ts delete mode 100644 packages/sandbox/tsconfig.build.json delete mode 100644 packages/sandbox/tsconfig.json delete mode 100644 pnpm-workspace.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5025839..a1d835d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,5 @@ jobs: - run: pnpm install --frozen-lockfile - run: pnpm fmt:check - - run: pnpm type-check:all - - run: pnpm test:all - - run: pnpm build:all + - run: pnpm type-check + - run: pnpm build diff --git a/CHANGELOG.md b/CHANGELOG.md index e7926a1..8fae008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,3 @@ # 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. diff --git a/README.md b/README.md index 52e0e94..85681ab 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ 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: @@ -18,12 +16,6 @@ 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. @@ -51,21 +43,6 @@ 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`. diff --git a/package.json b/package.json index 38ffc8d..4ce2a0b 100644 --- a/package.json +++ b/package.json @@ -20,15 +20,12 @@ ], "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", - "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" + "type-check": "tsc --noEmit" }, "peerDependencies": { "@bufbuild/protobuf": ">=2, <3" diff --git a/packages/sandbox/README.md b/packages/sandbox/README.md deleted file mode 100644 index f99bba6..0000000 --- a/packages/sandbox/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# @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`. diff --git a/packages/sandbox/package.json b/packages/sandbox/package.json deleted file mode 100644 index 377405f..0000000 --- a/packages/sandbox/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "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" - } -} diff --git a/packages/sandbox/src/client.ts b/packages/sandbox/src/client.ts deleted file mode 100644 index a5161c4..0000000 --- a/packages/sandbox/src/client.ts +++ /dev/null @@ -1,65 +0,0 @@ -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`. */ -export interface SandboxClient { - readonly rpc: Client - readonly endpoint: string -} diff --git a/packages/sandbox/src/command.test.ts b/packages/sandbox/src/command.test.ts deleted file mode 100644 index 11c3921..0000000 --- a/packages/sandbox/src/command.test.ts +++ /dev/null @@ -1,280 +0,0 @@ -import {create, type MessageInitShape} from '@bufbuild/protobuf' -import {timestampFromDate} from '@bufbuild/protobuf/wkt' -import assert from 'node:assert' -import test from 'node:test' -import {_commandInternals, SandboxCommandExecution, type SandboxCommandExecutionFinished} from './command.js' -import { - SandboxCommandExecutionStatus as CommandStatusProto, - SandboxCommandExecutionEvent_FinishedSchema, - SandboxCommandExecutionEvent_StartedSchema, - SandboxCommandExecutionEvent_StderrBytesSchema, - SandboxCommandExecutionEvent_StdoutBytesSchema, - SandboxCommandExecutionEventSchema, - SandboxCommandExecutionSchema, - type SandboxCommandExecutionEvent, -} from './gen/depot/sandbox/v1/command_pb.js' - -// These tests cover the round-trip between a wire proto and a SandboxCommandExecution -// instance. The proto-to-field mapping lives in one place, and if someone adds a new -// field they need to update both fromProto and applyProto. These tests catch the case -// where the constructor populates a field but applyProto forgets to refresh it. - -const STARTED_AT = new Date('2026-05-23T10:00:00.000Z') -const FINISHED_AT = new Date('2026-05-23T10:00:05.500Z') - -function makeProto(overrides: MessageInitShape = {}) { - return create(SandboxCommandExecutionSchema, { - cmdId: 'cmd_01H9X', - sandboxId: 'sbx_01H9X', - cmd: '/bin/echo', - args: ['hello', 'world'], - cwd: '/tmp', - env: {FOO: 'bar'}, - sudo: false, - detached: false, - status: CommandStatusProto.RUNNING, - startedAt: timestampFromDate(STARTED_AT), - stdoutBytesEmitted: 0n, - stderrBytesEmitted: 0n, - ...overrides, - }) -} - -test('SandboxCommandExecution.fromProto populates readonly fields from a wire-shape proto', () => { - const cmd = ( - SandboxCommandExecution as unknown as {fromProto: (p: ReturnType) => SandboxCommandExecution} - ).fromProto(makeProto()) - assert.equal(cmd.cmdId, 'cmd_01H9X') - assert.equal(cmd.sandboxId, 'sbx_01H9X') - assert.equal(cmd.cmd, '/bin/echo') - assert.deepEqual([...cmd.args], ['hello', 'world']) - assert.equal(cmd.cwd, '/tmp') - assert.deepEqual({...cmd.env}, {FOO: 'bar'}) - assert.equal(cmd.sudo, false) - assert.equal(cmd.detached, false) - assert.equal(cmd.startedAt.toISOString(), STARTED_AT.toISOString()) - assert.equal(cmd.status, 'running') - assert.equal(cmd.exitCode, undefined) - assert.equal(cmd.finishedAt, undefined) - assert.equal(cmd.stdoutBytesEmitted, 0) - assert.equal(cmd.stderrBytesEmitted, 0) -}) - -test('SandboxCommandExecution.applyProto refreshes mutable accessors on settle', () => { - type CmdInternal = SandboxCommandExecution & {applyProto(p: ReturnType): void} - const cmd = ( - SandboxCommandExecution as unknown as {fromProto: (p: ReturnType) => SandboxCommandExecution} - ).fromProto(makeProto()) - assert.equal(cmd.status, 'running') - assert.equal(cmd.exitCode, undefined) - ;(cmd as CmdInternal).applyProto( - makeProto({ - status: CommandStatusProto.FINISHED, - exitCode: 0, - finishedAt: timestampFromDate(FINISHED_AT), - stdoutBytesEmitted: 12n, - stderrBytesEmitted: 4n, - }), - ) - - assert.equal(cmd.status, 'finished') - assert.equal(cmd.exitCode, 0) - assert.equal(cmd.finishedAt?.toISOString(), FINISHED_AT.toISOString()) - assert.equal(cmd.stdoutBytesEmitted, 12) - assert.equal(cmd.stderrBytesEmitted, 4) -}) - -test('SandboxCommandExecutionFinished is a type-narrowed view of the same instance', () => { - type CmdInternal = SandboxCommandExecution & {applyProto(p: ReturnType): void} - const cmd = ( - SandboxCommandExecution as unknown as {fromProto: (p: ReturnType) => SandboxCommandExecution} - ).fromProto(makeProto()) - ;(cmd as CmdInternal).applyProto( - makeProto({ - status: CommandStatusProto.FINISHED, - exitCode: 1, - finishedAt: timestampFromDate(FINISHED_AT), - }), - ) - // SandboxCommandExecutionFinished is just the same instance viewed through a narrower - // type that marks exitCode and finishedAt as non-undefined. Casting to it preserves - // object identity, so callers don't have to re-check those fields after wait(). - const finished = cmd as SandboxCommandExecutionFinished - assert.strictEqual(finished, cmd) - assert.equal(finished.exitCode, 1) - assert.equal(finished.finishedAt.toISOString(), FINISHED_AT.toISOString()) -}) - -// The remaining tests cover the runCommand driver: how it ingests a stream of execution -// events and surfaces them through the accessors, logs(), output(), and wait(). - -function makeStartedEvent(cmdId: string, at: Date): SandboxCommandExecutionEvent { - return create(SandboxCommandExecutionEventSchema, { - event: { - case: 'started', - value: create(SandboxCommandExecutionEvent_StartedSchema, {cmdId, startedAt: timestampFromDate(at)}), - }, - }) -} - -function makeStdoutEvent(data: string, byteOffset: number): SandboxCommandExecutionEvent { - return create(SandboxCommandExecutionEventSchema, { - event: { - case: 'stdout', - value: create(SandboxCommandExecutionEvent_StdoutBytesSchema, { - data: new TextEncoder().encode(data), - byteOffset: BigInt(byteOffset), - timestamp: timestampFromDate(new Date()), - }), - }, - }) -} - -function makeStderrEvent(data: string, byteOffset: number): SandboxCommandExecutionEvent { - return create(SandboxCommandExecutionEventSchema, { - event: { - case: 'stderr', - value: create(SandboxCommandExecutionEvent_StderrBytesSchema, { - data: new TextEncoder().encode(data), - byteOffset: BigInt(byteOffset), - timestamp: timestampFromDate(new Date()), - }), - }, - }) -} - -function makeFinishedEvent(exitCode: number, at: Date): SandboxCommandExecutionEvent { - return create(SandboxCommandExecutionEventSchema, { - event: { - case: 'finished', - value: create(SandboxCommandExecutionEvent_FinishedSchema, {exitCode, finishedAt: timestampFromDate(at)}), - }, - }) -} - -test('runCommand driver: ingest pumps Startedβ†’stdoutβ†’Finished through to accessors and wait()', async () => { - const cmd = _commandInternals.fromStartedEvent({ - cmdId: 'cmd_01H9X', - sandboxId: 'sbx_01H9X', - cmd: '/bin/echo', - args: ['hello'], - cwd: undefined, - env: {}, - sudo: false, - detached: false, - startedAt: STARTED_AT, - }) - - _commandInternals.ingest(cmd, makeStartedEvent('cmd_01H9X', STARTED_AT)) - _commandInternals.ingest(cmd, makeStdoutEvent('hello\n', 6)) - _commandInternals.ingest(cmd, makeFinishedEvent(0, FINISHED_AT)) - _commandInternals.end(cmd) - - const finished = await cmd.wait() - assert.strictEqual(finished, cmd) - assert.equal(finished.exitCode, 0) - assert.equal(finished.finishedAt.toISOString(), FINISHED_AT.toISOString()) - assert.equal(cmd.status, 'finished') - assert.equal(cmd.stdoutBytesEmitted, 6) -}) - -test('runCommand driver: logs() yields decoded UTF-8 chunks per stream', async () => { - const cmd = _commandInternals.fromStartedEvent({ - cmdId: 'cmd_01H9Y', - sandboxId: 'sbx_01H9Y', - cmd: '/bin/sh', - args: ['-c', 'echo out; echo err 1>&2'], - cwd: undefined, - env: {}, - sudo: false, - detached: false, - startedAt: STARTED_AT, - }) - - _commandInternals.ingest(cmd, makeStartedEvent('cmd_01H9Y', STARTED_AT)) - _commandInternals.ingest(cmd, makeStdoutEvent('out\n', 4)) - _commandInternals.ingest(cmd, makeStderrEvent('err\n', 4)) - _commandInternals.ingest(cmd, makeFinishedEvent(0, FINISHED_AT)) - _commandInternals.end(cmd) - - const chunks: Array<{stream: string; data: string}> = [] - for await (const c of cmd.logs()) chunks.push({stream: c.stream, data: c.data}) - assert.deepEqual(chunks, [ - {stream: 'stdout', data: 'out\n'}, - {stream: 'stderr', data: 'err\n'}, - ]) -}) - -test('runCommand driver: output("stdout") returns concatenated stdout only', async () => { - const cmd = _commandInternals.fromStartedEvent({ - cmdId: 'cmd_01H9Z', - sandboxId: 'sbx_01H9Z', - cmd: '/bin/sh', - args: [], - cwd: undefined, - env: {}, - sudo: false, - detached: false, - startedAt: STARTED_AT, - }) - - _commandInternals.ingest(cmd, makeStartedEvent('cmd_01H9Z', STARTED_AT)) - _commandInternals.ingest(cmd, makeStdoutEvent('hello ', 6)) - _commandInternals.ingest(cmd, makeStderrEvent('boom\n', 5)) - _commandInternals.ingest(cmd, makeStdoutEvent('world\n', 12)) - _commandInternals.ingest(cmd, makeFinishedEvent(0, FINISHED_AT)) - _commandInternals.end(cmd) - - assert.equal(await cmd.output('stdout'), 'hello world\n') - assert.equal(await cmd.output('stderr'), 'boom\n') - assert.equal(await cmd.output('both'), 'hello boom\nworld\n') -}) - -test('runCommand driver: wait() throws when stream surfaces an Error event', async () => { - const cmd = _commandInternals.fromStartedEvent({ - cmdId: 'cmd_01HBAD', - sandboxId: 'sbx_01HBAD', - cmd: '/bin/false', - args: [], - cwd: undefined, - env: {}, - sudo: false, - detached: false, - startedAt: STARTED_AT, - }) - _commandInternals.ingest(cmd, makeStartedEvent('cmd_01HBAD', STARTED_AT)) - _commandInternals.ingest( - cmd, - create(SandboxCommandExecutionEventSchema, {event: {case: 'error', value: {reason: 'vm vanished'}}}), - ) - _commandInternals.end(cmd) - - await assert.rejects(() => cmd.wait(), /vm vanished/) -}) - -test('detached command: logs(), output(), and wait() are unavailable', async () => { - // A detached command runs fire-and-forget; the driver ingests Started and - // then returns without draining, so the SDK holds no output for it. All three - // output-facing accessors must reject rather than hang or return empty. - const cmd = _commandInternals.fromStartedEvent({ - cmdId: 'cmd_01HDETACH', - sandboxId: 'sbx_01HDETACH', - cmd: '/bin/sleep', - args: ['3600'], - cwd: undefined, - env: {}, - sudo: false, - detached: true, - startedAt: STARTED_AT, - }) - _commandInternals.ingest(cmd, makeStartedEvent('cmd_01HDETACH', STARTED_AT)) - - assert.equal(cmd.detached, true) - await assert.rejects(() => cmd.wait(), /detached/) - await assert.rejects(() => cmd.output(), /detached/) - await assert.rejects(async () => { - for await (const _ of cmd.logs()) { - // unreachable β€” logs() throws as soon as it is iterated - } - }, /detached/) -}) diff --git a/packages/sandbox/src/command.ts b/packages/sandbox/src/command.ts deleted file mode 100644 index 400ef5d..0000000 --- a/packages/sandbox/src/command.ts +++ /dev/null @@ -1,561 +0,0 @@ -import {timestampDate, type Timestamp} from '@bufbuild/protobuf/wkt' -import { - SandboxCommandExecutionStatus as CommandStatusProto, - type SandboxCommandExecutionEvent, - type SandboxCommandExecution as SandboxCommandExecutionProto, -} from './gen/depot/sandbox/v1/command_pb.js' -import {BufferedEventLog} from './k-streaming.js' -import type {SandboxCommandExecutionStatus} from './types.js' - -/** - * A single decoded chunk of output yielded by {@link SandboxCommandExecution.logs}. Output - * arrives over the wire as raw bytes; the SDK decodes it as UTF-8 here before handing it - * to you. - */ -export interface SandboxCommandExecutionLogChunk { - stream: 'stdout' | 'stderr' - data: string - /** - * The running total of bytes seen on this stream, up to and including this chunk. You can - * use it to resume from where you left off when re-attaching to a command. - */ - byteOffset: number - timestamp: Date -} - -/** - * A command running in a sandbox, or one that has already finished. You get one back from - * {@link Sandbox.runCommand}. - * - * Once the command finishes, the same instance can be viewed through the narrower - * {@link SandboxCommandExecutionFinished} type, which guarantees `exitCode` and `finishedAt` - * are present. The object identity stays the same; `wait()` simply returns that narrower type. - */ -export class SandboxCommandExecution { - /** Unique identifier for this command, assigned by the server. */ - readonly cmdId: string - readonly sandboxId: string - readonly cmd: string - readonly args: ReadonlyArray - readonly cwd: string | undefined - readonly env: Readonly> - readonly sudo: boolean - readonly detached: boolean - readonly startedAt: Date - - protected _status: SandboxCommandExecutionStatus - protected _exitCode: number | undefined - protected _finishedAt: Date | undefined - protected _stdoutBytesEmitted: number - protected _stderrBytesEmitted: number - - // The event log for this command. Every `logs()` consumer reads from it, and `wait()` - // observes it to learn when the command settles. As the command runs, the background task - // started by `Sandbox.runCommand` feeds events from the server stream into this log. - // - // The log keeps a bounded replay buffer so a consumer that attaches late, or falls a little - // behind, can still catch up on recent events. A consumer that falls too far behind for the - // buffer to hold its place will receive a SlowConsumerError. - protected readonly events: BufferedEventLog - - // A promise that resolves (or rejects) when the command reaches a terminal state. This is - // kept separate from the event log on purpose: wait() waits on this promise instead of - // reading the log as an ordinary consumer. An ordinary consumer advances past each event it - // reads, which would let the replay buffer drop early output that no one has read yet β€” so a - // later output() or logs() call would only see the tail of the output rather than the whole - // thing. - protected _terminalPromise: Promise - protected _terminalResolve!: () => void - protected _terminalReject!: (err: unknown) => void - protected _terminalSettled = false - // The reason from the most recent mid-stream error event, if any. We remember it so that if - // the stream ends before a Finished event arrives, we can report that original error rather - // than a generic "stream closed without a Finished event" message. - protected _lastErrorReason: string | undefined - - protected constructor(opts: { - cmdId: string - sandboxId: string - cmd: string - args: string[] - cwd: string | undefined - env: Record - sudo: boolean - detached: boolean - startedAt: Date - status: SandboxCommandExecutionStatus - stdoutBytesEmitted: number - stderrBytesEmitted: number - }) { - this.cmdId = opts.cmdId - this.sandboxId = opts.sandboxId - this.cmd = opts.cmd - this.args = Object.freeze([...opts.args]) - this.cwd = opts.cwd - this.env = Object.freeze({...opts.env}) - this.sudo = opts.sudo - this.detached = opts.detached - this.startedAt = opts.startedAt - this._status = opts.status - this._stdoutBytesEmitted = opts.stdoutBytesEmitted - this._stderrBytesEmitted = opts.stderrBytesEmitted - // Size each buffered event by the size of its output payload, so the SDK measures the - // replay buffer the same way the server does. This keeps byte accounting consistent and - // accurate even though the SDK doesn't impose its own cap. - this.events = new BufferedEventLog({ - byteCostOf: (e) => commandEventByteCost(e), - }) - this._terminalPromise = new Promise((resolve, reject) => { - this._terminalResolve = resolve - this._terminalReject = reject - }) - } - - // Settle the terminal promise, but only the first time. Later calls do nothing, so a - // Finished event followed by a stream failure (or the reverse) won't try to settle it twice. - // When settling with an error, attach a no-op .catch so Node doesn't warn about an unhandled - // rejection if the caller never calls wait() β€” calling wait() is optional. - protected settleTerminal(err?: unknown): void { - if (this._terminalSettled) return - this._terminalSettled = true - if (err !== undefined) { - this._terminalReject(err) - this._terminalPromise.catch(() => {}) - } else { - this._terminalResolve() - } - } - - /** The command's current status. It reaches `finished`, `failed`, or `killed` once the command ends. */ - get status(): SandboxCommandExecutionStatus { - return this._status - } - - /** The command's exit code once it has finished, or undefined while it is still running. */ - get exitCode(): number | undefined { - return this._exitCode - } - - /** The time the command finished, or undefined while it is still running. */ - get finishedAt(): Date | undefined { - return this._finishedAt - } - - /** The total number of stdout bytes the server has produced so far. */ - get stdoutBytesEmitted(): number { - return this._stdoutBytesEmitted - } - - /** The total number of stderr bytes the server has produced so far. */ - get stderrBytesEmitted(): number { - return this._stderrBytesEmitted - } - - /** - * Stream the command's output as decoded UTF-8 chunks, with stdout and stderr interleaved in - * the order they arrived. - * - * You can call this more than once. Each call returns its own independent iterator over the - * same shared output, so several consumers can read the logs at once without interfering with - * one another. A consumer that attaches after the command has started will replay whatever - * output is still held in the replay buffer before catching up to live output. A consumer - * that reads too slowly may fall behind what the buffer can hold, in which case its iterator - * fails with a SlowConsumerError. - */ - async *logs(): AsyncGenerator { - if (this.detached) throw detachedUnavailable() - // Decode each stream incrementally with its own decoder so that a multi-byte UTF-8 - // character split across two chunks still decodes correctly instead of turning into - // replacement characters at the chunk boundary. Each call to logs() keeps its own decoders, - // so concurrent consumers don't share decoding state. - const stdoutDecoder = new TextDecoder('utf-8', {fatal: false}) - const stderrDecoder = new TextDecoder('utf-8', {fatal: false}) - // Track the latest byte offset for each stream separately. When we flush a trailing partial - // character at the end, it needs to carry its own stream's offset rather than whichever - // stream happened to produce the most recent chunk. Per-stream offsets are what resume and - // progress tracking rely on, so they must not be mixed. - let lastStdoutOffset = 0 - let lastStderrOffset = 0 - for await (const event of this.events.iterate()) { - const chunk = decodeEvent(event, stdoutDecoder, stderrDecoder) - if (chunk) { - if (chunk.stream === 'stdout') lastStdoutOffset = chunk.byteOffset - else lastStderrOffset = chunk.byteOffset - yield chunk - } - } - // Once the stream ends, flush each decoder so any trailing partial UTF-8 character is - // emitted as a replacement character rather than silently dropped. - const stdoutTail = stdoutDecoder.decode() - const stderrTail = stderrDecoder.decode() - if (stdoutTail.length > 0) { - yield {stream: 'stdout', data: stdoutTail, byteOffset: lastStdoutOffset, timestamp: new Date()} - } - if (stderrTail.length > 0) { - yield {stream: 'stderr', data: stderrTail, byteOffset: lastStderrOffset, timestamp: new Date()} - } - } - - /** - * Wait for the command to finish. The returned value is typed as - * {@link SandboxCommandExecutionFinished}, so once it resolves you can read `exitCode` and - * `finishedAt` without checking for undefined. - */ - async wait(): Promise { - if (this.detached) throw detachedUnavailable() - // Wait on the terminal promise instead of reading the event log. Reading the log here would - // advance a consumer cursor, which could let the replay buffer drop early output during a - // quiet period β€” breaking the common pattern of `await cmd.wait()` followed by - // `await cmd.output()`. - await this._terminalPromise - // Only narrow the type once both fields are actually present. The narrowed type declares - // them as non-optional. A stream-level failure rejects the terminal promise, so we never - // reach this point in that case. - if (this._exitCode !== undefined && this._finishedAt !== undefined) { - return this as SandboxCommandExecutionFinished - } - throw new Error(`SandboxCommandExecution ${this.cmdId} stream closed without Finished event`) - } - - /** - * Collect the command's output, waiting until the command finishes. Pass `'both'` (the - * default) to get stdout and stderr interleaved in arrival order, or `'stdout'` / `'stderr'` - * to get just one stream. - * - * This reads the output and watches for the command to finish using a single iterator, - * rather than calling wait() first and replaying afterward. Replaying after wait() could miss - * early output that the replay buffer had since dropped during a quiet period. Reading it all - * in one pass keeps the iterator at the start of the output until output() has actually - * consumed each chunk, so the only thing that can ever truncate the result is the 16 MiB - * size limit. - */ - async output(which: 'both' | 'stdout' | 'stderr' = 'both'): Promise { - if (this.detached) throw detachedUnavailable() - const stdoutDecoder = new TextDecoder('utf-8', {fatal: false}) - const stderrDecoder = new TextDecoder('utf-8', {fatal: false}) - let out = '' - for await (const event of this.events.iterate({fromOffset: 0})) { - const chunk = decodeEvent(event, stdoutDecoder, stderrDecoder) - if (chunk && (which === 'both' || which === chunk.stream)) { - out += chunk.data - } - // An error event signals partial degradation mid-stream, not the end: the stream can - // still recover and go on to deliver a Finished event. So don't stop here β€” keep - // collecting output. If the stream really does close without finishing, the failure - // surfaces as an exception from this loop instead. - if (event.event.case === 'finished') break - } - // Flush the decoders so any trailing partial UTF-8 character is emitted as a replacement - // character rather than silently dropped. - if (which === 'both' || which === 'stdout') out += stdoutDecoder.decode() - if (which === 'both' || which === 'stderr') out += stderrDecoder.decode() - return out - } - - /** Shorthand for `output('stdout')`. */ - stdout(): Promise { - return this.output('stdout') - } - - /** Shorthand for `output('stderr')`. */ - stderr(): Promise { - return this.output('stderr') - } - - /** - * Build a {@link SandboxCommandExecution} from its wire representation. This is the single - * place that maps proto fields onto instance fields, mirroring how `Sandbox.fromProto` - * works. - * - * @internal - */ - protected static fromProto(proto: SandboxCommandExecutionProto): SandboxCommandExecution { - const instance = new SandboxCommandExecution({ - cmdId: proto.cmdId, - sandboxId: proto.sandboxId, - cmd: proto.cmd, - args: proto.args, - cwd: proto.cwd, - env: proto.env, - sudo: proto.sudo, - detached: proto.detached, - startedAt: requireTimestamp(proto.startedAt, 'SandboxCommandExecution.started_at'), - status: commandStatusFromProto(proto.status) ?? 'running', - stdoutBytesEmitted: Number(proto.stdoutBytesEmitted), - stderrBytesEmitted: Number(proto.stderrBytesEmitted), - }) - instance.applyProto(proto) - // This builds a point-in-time snapshot of a command, with no live stream feeding new events - // into its buffer. Close the buffer right away so that output() and logs() iterators - // terminate normally instead of waiting forever for events that will never arrive. - instance.events.end() - // Settle the terminal promise based on the snapshot's status: - // - finished: the command exited cleanly, so exit_code and finished_at are present. - // Resolve to the narrowed finished type. If either field is missing, the snapshot is - // malformed, so reject and say so. - // - failed / killed: terminal, but the process may never have produced an exit code (the - // wire contract only guarantees one for a finished command). wait() rejects for these - // either way, so reject with the status rather than calling a real failure malformed. - // - running / pending: not a terminal state. A static snapshot can never go on to finish, - // so there is nothing to wait for β€” reject. - if (instance._status === 'finished') { - if (instance._exitCode !== undefined && instance._finishedAt !== undefined) { - instance.settleTerminal() - } else { - instance.settleTerminal( - new Error(`SandboxCommandExecution ${instance.cmdId} snapshot is FINISHED but missing exit_code/finished_at`), - ) - } - } else if (instance._status === 'failed' || instance._status === 'killed') { - instance.settleTerminal(new Error(`SandboxCommandExecution ${instance.cmdId} ${instance._status}`)) - } else { - instance.settleTerminal( - new Error( - `SandboxCommandExecution ${instance.cmdId} snapshot is not in a terminal state (status=${instance._status})`, - ), - ) - } - return instance - } - - /** - * Construct an instance from the initial Started event, which carries only the command ID and - * start time, together with the caller's original options β€” the case where no full command - * proto is available yet. Same-package callers reach this through the {@link _commandInternals} - * helper. - * - * @internal - */ - protected static _fromStartedEvent(opts: { - cmdId: string - sandboxId: string - cmd: string - args: string[] - cwd: string | undefined - env: Record - sudo: boolean - detached: boolean - startedAt: Date - }): SandboxCommandExecution { - return new SandboxCommandExecution({ - cmdId: opts.cmdId, - sandboxId: opts.sandboxId, - cmd: opts.cmd, - args: opts.args, - cwd: opts.cwd, - env: opts.env, - sudo: opts.sudo, - detached: opts.detached, - startedAt: opts.startedAt, - status: 'running', - stdoutBytesEmitted: 0, - stderrBytesEmitted: 0, - }) - } - - /** - * Update this instance's mutable fields from a fresh proto returned by the server. The - * immutable identity fields are set once by the constructor; only the state-derived fields - * (status, exit code, byte counts, and so on) are refreshed here. - * - * @internal - */ - protected applyProto(proto: SandboxCommandExecutionProto): void { - this._status = commandStatusFromProto(proto.status) ?? this._status - this._exitCode = proto.exitCode - this._finishedAt = proto.finishedAt !== undefined ? timestampDate(proto.finishedAt) : undefined - this._stdoutBytesEmitted = Number(proto.stdoutBytesEmitted) - this._stderrBytesEmitted = Number(proto.stderrBytesEmitted) - } - - /** - * Apply a single event to this command: update the instance's mutable fields to reflect it, - * then append it to the event log. The events arrive in the sequence Started, then any number - * of Stdout and Stderr events, then Finished. Called by the background task that - * `Sandbox.runCommand` starts to consume the server's response stream. - * - * @internal - */ - protected _ingestEvent(event: SandboxCommandExecutionEvent): void { - const e = event.event - switch (e.case) { - case 'started': - this._status = 'running' - break - case 'stdout': - this._stdoutBytesEmitted = Number(e.value.byteOffset) - break - case 'stderr': - this._stderrBytesEmitted = Number(e.value.byteOffset) - break - case 'finished': - this._exitCode = e.value.exitCode - this._finishedAt = e.value.finishedAt !== undefined ? timestampDate(e.value.finishedAt) : new Date() - // The exit code is separate from the status. A Finished event means the process exited - // and produced an exit code, whatever that code is β€” even a non-zero one still counts as - // finished. The 'failed' status is reserved for cases where the process never produced - // an exit code at all, such as the host aborting or the stream dying; those arrive - // through the 'error' case below or through _failStream(). - this._status = 'finished' - this.settleTerminal() - break - case 'error': - // An error event signals partial degradation mid-stream; it is not the terminal marker. - // The stream can still deliver more output and eventually a Finished event. Leave the - // status unchanged so that code polling status() doesn't see the terminal 'failed' value - // for a command that ends up succeeding. Just remember the reason, so _endStream can use - // it if the stream ends without a Finished event. The command settles only on Finished, - // _endStream, or _failStream. - this._lastErrorReason = e.value.reason - break - case 'evicted': - case undefined: - break - } - this.events.append(event) - } - - /** - * Close the event log so any iterators that are still draining their queues can complete. - * Called by the background task when the server stream closes normally. - * - * @internal - */ - protected _endStream(): void { - this.events.end() - // If a Finished event already settled the command, this does nothing. Otherwise the stream - // closed cleanly but never delivered a Finished event, which counts as a failure. Use the - // last mid-stream error reason if one was seen, since it carries the more useful message, - // and otherwise fall back to a generic message. - const message = - this._lastErrorReason !== undefined - ? `SandboxCommandExecution ${this.cmdId} failed: ${this._lastErrorReason}` - : `SandboxCommandExecution ${this.cmdId} stream closed without Finished event` - this.settleTerminal(new Error(message)) - } - - /** - * Fail the event log so its iterators reject. Called by the background task when the server - * stream errors. - * - * @internal - */ - protected _failStream(err: unknown): void { - this._status = 'failed' - this.events.error(err) - this.settleTerminal(err) - } -} - -/** - * A typed view of a {@link SandboxCommandExecution} that has finished. It is the same runtime - * object that `wait()` was called on β€” identity is preserved β€” but its type narrows `exitCode` - * and `finishedAt` to be non-undefined, so you don't have to check for undefined after awaiting. - * - * This is deliberately an intersection type rather than a subclass. A subclass would only narrow - * at runtime if `wait()` built a new instance, which would break identity with any `logs()` - * consumers still holding the original reference. The intersection lets the return type of - * `wait()` narrow at the type level while `this` remains the one and only instance. - */ -export type SandboxCommandExecutionFinished = SandboxCommandExecution & { - readonly exitCode: number - readonly finishedAt: Date -} - -// A detached command runs fire-and-forget: the server never streams its output -// back and nothing is stored, so the SDK has no events to hand out. logs(), -// output(), and wait() raise this rather than hang forever or return empty. -function detachedUnavailable(): Error { - return new Error( - 'this command was run detached (beta): its output is not retained and cannot be retrieved yet, ' + - 'so logs(), output(), and wait() are unavailable', - ) -} - -function commandStatusFromProto(status: CommandStatusProto): SandboxCommandExecutionStatus | undefined { - switch (status) { - case CommandStatusProto.PENDING: - return 'pending' - case CommandStatusProto.RUNNING: - return 'running' - case CommandStatusProto.FINISHED: - return 'finished' - case CommandStatusProto.FAILED: - return 'failed' - case CommandStatusProto.KILLED: - return 'killed' - case CommandStatusProto.UNSPECIFIED: - return undefined - } -} - -function requireTimestamp(ts: Timestamp | undefined, field: string): Date { - if (ts === undefined) { - throw new Error(`SandboxCommandExecution proto missing required ${field}`) - } - return timestampDate(ts) -} - -function commandEventByteCost(event: SandboxCommandExecutionEvent): number { - const e = event.event - if (e.case === 'stdout' || e.case === 'stderr') return e.value.data.byteLength - return 0 -} - -function decodeEvent( - event: SandboxCommandExecutionEvent, - stdoutDecoder: InstanceType, - stderrDecoder: InstanceType, -): SandboxCommandExecutionLogChunk | null { - const e = event.event - if (e.case === 'stdout') { - return { - stream: 'stdout', - data: stdoutDecoder.decode(e.value.data, {stream: true}), - byteOffset: Number(e.value.byteOffset), - timestamp: e.value.timestamp !== undefined ? timestampDate(e.value.timestamp) : new Date(), - } - } - if (e.case === 'stderr') { - return { - stream: 'stderr', - data: stderrDecoder.decode(e.value.data, {stream: true}), - byteOffset: Number(e.value.byteOffset), - timestamp: e.value.timestamp !== undefined ? timestampDate(e.value.timestamp) : new Date(), - } - } - return null -} - -// A same-package entry point that lets `Sandbox.runCommand` create a SandboxCommandExecution -// from a Started event and feed events into its log. TypeScript only enforces `protected` -// within the class and its subclasses, so these helpers cast to a structural shape in order to -// call the underscore-prefixed methods. The leading underscore and the fact that this is not -// exported from index.ts keep it off the public API surface. It exists only for -// the runCommand machinery, not as a stable, supported API. -export const _commandInternals = { - fromStartedEvent(opts: { - cmdId: string - sandboxId: string - cmd: string - args: string[] - cwd: string | undefined - env: Record - sudo: boolean - detached: boolean - startedAt: Date - }): SandboxCommandExecution { - return ( - SandboxCommandExecution as unknown as {_fromStartedEvent(o: typeof opts): SandboxCommandExecution} - )._fromStartedEvent(opts) - }, - ingest(cmd: SandboxCommandExecution, event: SandboxCommandExecutionEvent): void { - ;(cmd as unknown as {_ingestEvent(e: SandboxCommandExecutionEvent): void})._ingestEvent(event) - }, - end(cmd: SandboxCommandExecution): void { - ;(cmd as unknown as {_endStream(): void})._endStream() - }, - fail(cmd: SandboxCommandExecution, err: unknown): void { - ;(cmd as unknown as {_failStream(err: unknown): void})._failStream(err) - }, -} diff --git a/packages/sandbox/src/errors.ts b/packages/sandbox/src/errors.ts deleted file mode 100644 index f9dc143..0000000 --- a/packages/sandbox/src/errors.ts +++ /dev/null @@ -1,142 +0,0 @@ -// Error types surfaced by @depot/sandbox. -// -// Surfaced from command-output consumer iteration and the file system surface. -// Wider sets of SDK errors can be added as new failure modes are exposed. - -import {ConnectError} from '@connectrpc/connect' -import { - FileSystemErrorCode as FileSystemErrorCodeProto, - FileSystemErrorDetailSchema, -} from './gen/depot/sandbox/v1/filesystem_pb.js' - -/** - * Thrown into a `SandboxCommandExecution.logs()` iterator when its bounded queue - * overflows because the producer (the underlying RPC stream) outran the - * consumer's drain rate. Other consumers on the same command (and the - * underlying stream) are unaffected. - */ -export class SlowConsumerError extends Error { - override readonly name = 'SlowConsumerError' - constructor(message = 'event consumer queue overflow') { - super(message) - } -} - -// File system errors. - -/** - * The Node-style fs error codes the SDK exposes, as a string union. These match - * the codes Node throws on a real `node:fs/promises` failure, so existing - * `if (err.code === 'ENOENT')` checks keep working against a sandbox file - * system. `OTHER` is the catch-all the server uses when it can't classify a - * failure into a specific code. - */ -export type FileSystemErrorCode = - | 'EPERM' - | 'ENOENT' - | 'EINTR' - | 'EIO' - | 'EBADF' - | 'ENOMEM' - | 'EACCES' - | 'EBUSY' - | 'EEXIST' - | 'EXDEV' - | 'ENOTDIR' - | 'EISDIR' - | 'EINVAL' - | 'ENFILE' - | 'EMFILE' - | 'ETXTBSY' - | 'EFBIG' - | 'ENOSPC' - | 'EROFS' - | 'EMLINK' - | 'ENAMETOOLONG' - | 'ENOTEMPTY' - | 'ELOOP' - | 'EOPNOTSUPP' - | 'EDQUOT' - | 'OTHER' - -// The proto enum value of each code IS its Linux errno number (OTHER is an -// off-band sentinel), so the generated enum is the only mapping we need β€” read -// in both directions, with no hand-maintained code<->number or code<->errno -// table to drift from the wire. -const PROTO_PREFIX = 'FILESYSTEM_ERROR_CODE_' - -// Wire value (an errno number, or the OTHER sentinel) -> friendly string code, -// taken straight from the generated enum's own reverse lookup. UNSPECIFIED, the -// OTHER sentinel, and any value this build doesn't recognize (a newer peer -// sending a code we don't know) all read as OTHER, so the caller still gets a -// usable error. -function codeFromProto(value: FileSystemErrorCodeProto): FileSystemErrorCode { - const name = (FileSystemErrorCodeProto as Record)[value] - if (name === undefined || value === FileSystemErrorCodeProto.FILESYSTEM_ERROR_CODE_UNSPECIFIED) return 'OTHER' - const stripped = name.slice(PROTO_PREFIX.length) - return stripped === 'OTHER' ? 'OTHER' : (stripped as FileSystemErrorCode) -} - -// Negated Linux errno for a code, for Node-shape parity. The code's proto value -// is its errno, so this is just the negation; OTHER (and anything without a -// real errno) reads as -1, matching libuv's "no match". This is the sandbox's -// errno β€” the file system runs on Linux β€” and `code` is the portable key. -function errnoForCode(code: FileSystemErrorCode): number { - if (code === 'OTHER') return -1 - const value = (FileSystemErrorCodeProto as unknown as Record)[PROTO_PREFIX + code] - return value === undefined || value <= 0 ? -1 : -value -} - -/** - * A Node-shape file system error, thrown by every `sandbox.fs.*` method on - * failure. It carries the same `code`/`syscall`/`path`/`errno` fields Node - * attaches to its own fs errors, so code written against `node:fs/promises` - * can branch on `err.code` without caring that the operation crossed the wire. - */ -export class FileSystemError extends Error { - /** Node-style error code, for example `'ENOENT'`. */ - readonly code: FileSystemErrorCode - /** The operation that failed, for example `'stat'` or `'open'`. */ - readonly syscall: string - /** The path the operation was attempted on. */ - readonly path: string - /** Negated Linux errno for the code, for Node-shape parity. */ - readonly errno: number - - constructor(opts: {code: FileSystemErrorCode; syscall: string; path: string; message?: string}) { - super(opts.message ?? `${opts.code}: ${opts.syscall} '${opts.path}'`) - this.name = 'FileSystemError' - this.code = opts.code - this.syscall = opts.syscall - this.path = opts.path - this.errno = errnoForCode(opts.code) - } -} - -/** - * Turn a thrown RPC error into a `FileSystemError`. The server attaches a - * `FileSystemErrorDetail` to the Connect error; this reads it back with - * `findDetails` and rebuilds the Node-shape error. When no detail is attached - * (a transport failure, or a server that didn't classify the error), it falls - * back to an `OTHER` error carrying the supplied syscall and path so the caller - * still gets a `FileSystemError` rather than a raw `ConnectError`. - * - * @internal Used by the FileSystem class to remap every RPC rejection. - */ -export function toFileSystemError(err: unknown, syscall: string, path: string): FileSystemError { - if (err instanceof ConnectError) { - const details = err.findDetails(FileSystemErrorDetailSchema) - const detail = details[0] - if (detail) { - return new FileSystemError({ - code: codeFromProto(detail.code), - syscall: detail.syscall || syscall, - path: detail.path || path, - message: detail.message, - }) - } - return new FileSystemError({code: 'OTHER', syscall, path, message: err.rawMessage}) - } - const message = err instanceof Error ? err.message : String(err) - return new FileSystemError({code: 'OTHER', syscall, path, message}) -} diff --git a/packages/sandbox/src/filesystem.test.ts b/packages/sandbox/src/filesystem.test.ts deleted file mode 100644 index 325050a..0000000 --- a/packages/sandbox/src/filesystem.test.ts +++ /dev/null @@ -1,356 +0,0 @@ -import {create} from '@bufbuild/protobuf' -import {Code, ConnectError} from '@connectrpc/connect' -import assert from 'node:assert' -import test from 'node:test' -import type {SandboxClient} from './client.js' -import {FileSystemError} from './errors.js' -import {DirEntry, FileSystem, StatResult} from './filesystem.js' -import { - FileChunkSchema, - FileSystemErrorCode, - FileSystemErrorDetailSchema, - FileType, - ReadDirResponseSchema, - StatResponseSchema, - type WriteFileRequest, - WriteFileResponseSchema, -} from './gen/depot/sandbox/v1/filesystem_pb.js' - -// A fake SandboxClient whose rpc methods record the last request they received -// and either return a scripted response or throw a scripted error. Only the -// methods the FileSystem class touches are stubbed; everything else throws if -// called, so a test that hits an unexpected method fails loudly. -function fakeClient(overrides: Partial unknown>>): { - client: SandboxClient - lastRequest: () => unknown -} { - let last: unknown - const handler = (name: string) => { - const fn = overrides[name] - if (!fn) throw new Error(`unexpected RPC call: ${name}`) - return (req: unknown) => { - last = req - return fn(req) - } - } - const rpc = new Proxy( - {}, - { - get(_target, prop: string) { - return handler(prop) - }, - }, - ) - return { - client: {rpc, endpoint: 'http://test'} as unknown as SandboxClient, - lastRequest: () => last, - } -} - -// Build a ConnectError carrying a FileSystemErrorDetail, as the server sends. -function connectErrorWithDetail( - code: FileSystemErrorCode, - syscall: string, - path: string, - message?: string, -): ConnectError { - const detail = create(FileSystemErrorDetailSchema, {code, syscall, path, message}) - return new ConnectError(message ?? 'failure', Code.FailedPrecondition, undefined, [ - {desc: FileSystemErrorDetailSchema, value: detail}, - ]) -} - -test('mkdir sends the request against the bound sandbox', async () => { - const recording = fakeClient({mkdir: () => ({})}) - const fs = new FileSystem({client: recording.client, sandboxId: 'sbx_1'}) - await fs.mkdir('/work', {recursive: true, mode: 0o755}) - assert.deepEqual(recording.lastRequest(), { - sandbox: {selector: {case: 'id', value: 'sbx_1'}}, - path: '/work', - recursive: true, - mode: 0o755, - }) -}) - -test('stat maps the response into a StatResult with isFile/isDirectory', async () => { - const {client} = fakeClient({ - stat: () => - create(StatResponseSchema, { - path: '/f', - size: 2048n, - mode: 0o644, - type: FileType.FILE, - uname: 'alice', - gname: 'staff', - mtimeUnixSeconds: 1700000000n, - }), - }) - const fs = new FileSystem({client, sandboxId: 'sbx_1'}) - const stat = await fs.stat('/f') - assert.ok(stat instanceof StatResult) - assert.equal(stat.size, 2048) - assert.equal(stat.mode, 0o644) - assert.equal(stat.uname, 'alice') - assert.equal(stat.mtime.getTime(), 1700000000 * 1000) - assert.equal(stat.isFile(), true) - assert.equal(stat.isDirectory(), false) -}) - -test('readdir returns names by default and DirEntry[] with withFileTypes', async () => { - const response = create(ReadDirResponseSchema, { - entries: [ - {name: 'a.txt', type: FileType.FILE}, - {name: 'sub', type: FileType.DIRECTORY}, - ], - }) - const fs = new FileSystem({client: fakeClient({readDir: () => response}).client, sandboxId: 'sbx_1'}) - const names = await fs.readdir('/d') - assert.deepEqual(names, ['a.txt', 'sub']) - - const fs2 = new FileSystem({client: fakeClient({readDir: () => response}).client, sandboxId: 'sbx_1'}) - const entries = await fs2.readdir('/d', {withFileTypes: true}) - assert.ok((entries[0] as DirEntry) instanceof DirEntry) - assert.equal((entries[1] as DirEntry).isDirectory(), true) -}) - -test('readFile concatenates streamed chunks and decodes with an encoding', async () => { - async function* chunks() { - yield create(FileChunkSchema, {data: new TextEncoder().encode('hello ')}) - yield create(FileChunkSchema, {data: new TextEncoder().encode('world')}) - yield create(FileChunkSchema, {data: new Uint8Array(0), eof: true}) - } - const fs = new FileSystem({client: fakeClient({readFile: () => chunks()}).client, sandboxId: 'sbx_1'}) - const text = await fs.readFile('/f', {encoding: 'utf-8'}) - assert.equal(text, 'hello world') -}) - -test('writeFile streams an init message then the data', async () => { - const drained: WriteFileRequest[] = [] - const recording = fakeClient({ - writeFile: async (req) => { - // Drain the request stream so the SDK's generator runs and we can inspect it. - for await (const m of req as AsyncIterable) drained.push(m) - return create(WriteFileResponseSchema, {bytesWritten: BigInt(drained.length)}) - }, - }) - const fs = new FileSystem({client: recording.client, sandboxId: 'sbx_1'}) - await fs.writeFile('/f', 'hi', {recursive: true, mode: 0o600}) - - // Exactly two messages: the init naming the file and its options, then the bytes. - assert.equal(drained.length, 2) - const init = drained[0]?.input - assert.equal(init?.case, 'init') - if (init?.case === 'init') { - assert.equal(init.value.path, '/f') - assert.equal(init.value.mode, 0o600) - assert.equal(init.value.append, false) - assert.equal(init.value.createDirectories, true) - } - const data = drained[1]?.input - assert.equal(data?.case, 'data') - if (data?.case === 'data') { - assert.equal(new TextDecoder().decode(data.value), 'hi') - } -}) - -test('writeFile splits a large payload into frames under the gRPC message ceiling', async () => { - // A payload bigger than one gRPC message must arrive as several data frames, - // none larger than 4 MiB, behind a single init β€” so a gRPC client of this - // same stream wouldn't reject an oversized message. - const GRPC_MAX = 1024 * 1024 * 4 - const drained: WriteFileRequest[] = [] - const recording = fakeClient({ - writeFile: async (req) => { - for await (const m of req as AsyncIterable) drained.push(m) - return create(WriteFileResponseSchema, {bytesWritten: 0n}) - }, - }) - const fs = new FileSystem({client: recording.client, sandboxId: 'sbx_1'}) - const size = GRPC_MAX * 2 + 99 - await fs.writeFile('/big', new Uint8Array(size)) - - assert.equal(drained.filter((m) => m.input.case === 'init').length, 1) - const dataFrames = drained.filter((m) => m.input.case === 'data') - assert.ok(dataFrames.length > 1, 'expected the payload to be split across multiple frames') - let total = 0 - for (const m of dataFrames) { - if (m.input.case === 'data') { - assert.ok(m.input.value.byteLength <= GRPC_MAX, 'no frame may exceed the gRPC ceiling') - total += m.input.value.byteLength - } - } - assert.equal(total, size) -}) - -test('truncate rejects a negative size with EINVAL before hitting the wire', async () => { - let called = false - const fs = new FileSystem({ - client: fakeClient({ - truncate: () => { - called = true - return {} - }, - }).client, - sandboxId: 'sbx_1', - }) - await assert.rejects( - () => fs.truncate('/f', -1), - (err: unknown) => err instanceof FileSystemError && err.code === 'EINVAL', - ) - assert.equal(called, false) -}) - -test('a ConnectError with a detail becomes a Node-shape FileSystemError', async () => { - const fs = new FileSystem({ - client: fakeClient({ - stat: () => { - throw connectErrorWithDetail(FileSystemErrorCode.FILESYSTEM_ERROR_CODE_ENOENT, 'stat', '/missing') - }, - }).client, - sandboxId: 'sbx_1', - }) - await assert.rejects( - () => fs.stat('/missing'), - (err: unknown) => { - assert.ok(err instanceof FileSystemError) - assert.equal(err.code, 'ENOENT') - assert.equal(err.syscall, 'stat') - assert.equal(err.path, '/missing') - assert.equal(err.errno, -2) - return true - }, - ) -}) - -test('code and errno are derived from the enum value (no hand-maintained table)', async () => { - // ENOSPC's enum value is its Linux errno (28), so the negated errno falls out - // for free β€” proving the derivation works for codes beyond the original set. - const full = new FileSystem({ - client: fakeClient({ - stat: () => { - throw connectErrorWithDetail(FileSystemErrorCode.FILESYSTEM_ERROR_CODE_ENOSPC, 'write', '/disk') - }, - }).client, - sandboxId: 'sbx_1', - }) - await assert.rejects( - () => full.stat('/disk'), - (err: unknown) => err instanceof FileSystemError && err.code === 'ENOSPC' && err.errno === -28, - ) - - // OTHER is the off-band sentinel: no real errno, so it reads as -1. - const other = new FileSystem({ - client: fakeClient({ - stat: () => { - throw connectErrorWithDetail(FileSystemErrorCode.FILESYSTEM_ERROR_CODE_OTHER, 'stat', '/x') - }, - }).client, - sandboxId: 'sbx_1', - }) - await assert.rejects( - () => other.stat('/x'), - (err: unknown) => err instanceof FileSystemError && err.code === 'OTHER' && err.errno === -1, - ) -}) - -test('a ConnectError without a detail still yields an OTHER FileSystemError', async () => { - const fs = new FileSystem({ - client: fakeClient({ - mkdir: () => { - throw new ConnectError('transport blew up', Code.Unavailable) - }, - }).client, - sandboxId: 'sbx_1', - }) - await assert.rejects( - () => fs.mkdir('/x'), - (err: unknown) => err instanceof FileSystemError && err.code === 'OTHER' && err.syscall === 'mkdir', - ) -}) - -test('exists returns false on ENOENT and true otherwise', async () => { - const missing = new FileSystem({ - client: fakeClient({ - stat: () => { - throw connectErrorWithDetail(FileSystemErrorCode.FILESYSTEM_ERROR_CODE_ENOENT, 'stat', '/nope') - }, - }).client, - sandboxId: 'sbx_1', - }) - assert.equal(await missing.exists('/nope'), false) - - const present = new FileSystem({ - client: fakeClient({stat: () => create(StatResponseSchema, {path: '/yes', type: FileType.FILE})}).client, - sandboxId: 'sbx_1', - }) - assert.equal(await present.exists('/yes'), true) -}) - -test('exists rethrows a non-ENOENT error rather than reporting absence', async () => { - const fs = new FileSystem({ - client: fakeClient({ - stat: () => { - throw connectErrorWithDetail(FileSystemErrorCode.FILESYSTEM_ERROR_CODE_EACCES, 'stat', '/secret') - }, - }).client, - sandboxId: 'sbx_1', - }) - await assert.rejects( - () => fs.exists('/secret'), - (err: unknown) => err instanceof FileSystemError && err.code === 'EACCES', - ) -}) - -test('realpath throws ELOOP after exhausting the symlink hop limit', async () => { - // Every lstat reports a symlink and every readlink points back into the - // chain, so the walk never resolves and must bail with ELOOP, not OTHER. - const fs = new FileSystem({ - client: fakeClient({ - stat: () => create(StatResponseSchema, {path: '/loop', type: FileType.SYMLINK}), - readlink: () => ({target: '/loop'}), - }).client, - sandboxId: 'sbx_1', - }) - await assert.rejects( - () => fs.realpath('/loop'), - (err: unknown) => err instanceof FileSystemError && err.code === 'ELOOP', - ) -}) - -test('chmod accepts an octal string and converts it', async () => { - const recording = fakeClient({chmod: () => ({})}) - const fs = new FileSystem({client: recording.client, sandboxId: 'sbx_1'}) - await fs.chmod('/f', '755') - assert.deepEqual(recording.lastRequest(), { - sandbox: {selector: {case: 'id', value: 'sbx_1'}}, - path: '/f', - mode: 0o755, - }) -}) - -test('chmod rejects an invalid octal string with EINVAL and never hits the wire', async () => { - let called = false - const fs = new FileSystem({ - client: fakeClient({ - chmod: () => { - called = true - return {} - }, - }).client, - sandboxId: 'sbx_1', - }) - // '9' is not an octal digit, so parseInt('999', 8) is NaN. - await assert.rejects( - () => fs.chmod('/f', '999'), - (err: unknown) => err instanceof FileSystemError && err.code === 'EINVAL', - ) - assert.equal(called, false) -}) - -test('chmod rejects an out-of-range numeric mode with EINVAL', async () => { - const fs = new FileSystem({client: fakeClient({chmod: () => ({})}).client, sandboxId: 'sbx_1'}) - await assert.rejects( - () => fs.chmod('/f', 0o10000), - (err: unknown) => err instanceof FileSystemError && err.code === 'EINVAL', - ) -}) diff --git a/packages/sandbox/src/filesystem.ts b/packages/sandbox/src/filesystem.ts deleted file mode 100644 index 1ffd463..0000000 --- a/packages/sandbox/src/filesystem.ts +++ /dev/null @@ -1,528 +0,0 @@ -// The per-sandbox FileSystem surface. -// -// Methods mirror `node:fs/promises` so callers can drop the SDK in alongside -// existing fs code with little friction. Every method calls one SandboxService -// method against the bound sandbox and remaps a failure into a Node-shape -// FileSystemError (see errors.ts). A few methods are composed on the client -// from the primitives the server exposes β€” exists (stat + catch ENOENT), -// realpath (a readlink walk), mkdtemp (mkdir with a random suffix), appendFile -// (writeFile with append) β€” because the server intentionally doesn't ship a -// dedicated method for them. -// -// A FileSystem is bound to one sandbox and one client at construction; get it -// via `sandbox.fs()`. - -import {create} from '@bufbuild/protobuf' -import type {SandboxClient} from './client.js' -import {FileSystemError, toFileSystemError} from './errors.js' -import {FileType, WriteFileRequestSchema} from './gen/depot/sandbox/v1/filesystem_pb.js' - -// gRPC's default max message size is 4 MiB. The Connect HTTP transport the SDK -// uses doesn't impose that ceiling, but a gRPC client of this same WriteFile -// stream would, so split the payload into frames a little under 4 MiB β€” leaving -// room for the proto envelope around the bytes β€” rather than sending it as a -// single message a gRPC peer could reject. -const WRITE_CHUNK_BYTES = 1024 * 1024 * 4 - 64 * 1024 - -// Reject a permission mode that isn't a whole number of mode bits before it -// crosses the wire. An invalid octal string (chmod accepts strings) parses to -// NaN, and the server's `& 0o7777` would mask that to 0 β€” silently applying -// mode 000 instead of failing. Throw EINVAL up front, the way Node rejects an -// out-of-range mode, so the caller sees a clear error instead of a clobbered -// file. -function assertValidMode(mode: number, syscall: string, path: string): void { - if (!Number.isInteger(mode) || mode < 0 || mode > 0o7777) { - throw new FileSystemError({code: 'EINVAL', syscall, path, message: `${syscall} '${path}': invalid mode`}) - } -} - -/** The SDK-facing file type, mapped from the proto FileType enum. */ -export type FileTypeName = 'file' | 'directory' | 'symlink' | 'block' | 'char' | 'fifo' | 'socket' | 'unknown' - -function fileTypeName(type: FileType): FileTypeName { - switch (type) { - case FileType.FILE: - return 'file' - case FileType.DIRECTORY: - return 'directory' - case FileType.SYMLINK: - return 'symlink' - case FileType.BLOCK_DEVICE: - return 'block' - case FileType.CHARACTER_DEVICE: - return 'char' - case FileType.FIFO: - return 'fifo' - case FileType.SOCKET: - return 'socket' - case FileType.UNSPECIFIED: - default: - return 'unknown' - } -} - -/** - * The result of {@link FileSystem.stat}. Mirrors enough of a `node:fs/promises` - * Stats object that callers can call `.isDirectory()`/`.isFile()` and read - * size, mode, owner, and mtime without caring the value crossed the wire. - */ -export class StatResult { - readonly path: string - readonly size: number - /** POSIX permission bits as an octal integer (for example `0o644`). */ - readonly mode: number - readonly uname: string - readonly gname: string - readonly mtime: Date - readonly type: FileTypeName - - constructor(opts: { - path: string - size: number - mode: number - uname: string - gname: string - mtime: Date - type: FileTypeName - }) { - this.path = opts.path - this.size = opts.size - this.mode = opts.mode - this.uname = opts.uname - this.gname = opts.gname - this.mtime = opts.mtime - this.type = opts.type - } - - isFile(): boolean { - return this.type === 'file' - } - isDirectory(): boolean { - return this.type === 'directory' - } - /** True only when the value came from {@link FileSystem.lstat}. */ - isSymbolicLink(): boolean { - return this.type === 'symlink' - } - isBlockDevice(): boolean { - return this.type === 'block' - } - isCharacterDevice(): boolean { - return this.type === 'char' - } - isFIFO(): boolean { - return this.type === 'fifo' - } - isSocket(): boolean { - return this.type === 'socket' - } -} - -/** A directory entry returned by {@link FileSystem.readdir} with `withFileTypes`. */ -export class DirEntry { - readonly name: string - readonly type: FileTypeName - constructor(opts: {name: string; type: FileTypeName}) { - this.name = opts.name - this.type = opts.type - } - isFile(): boolean { - return this.type === 'file' - } - isDirectory(): boolean { - return this.type === 'directory' - } - isSymbolicLink(): boolean { - return this.type === 'symlink' - } -} - -export interface ReadFileOpts { - /** Decode the bytes as a string under this encoding. Omit to get a Buffer. */ - encoding?: BufferEncoding -} - -export interface WriteFileOpts { - /** POSIX mode applied if the file is created. */ - mode?: number - /** Create any missing parent directories before writing. */ - recursive?: boolean -} - -export interface MkdirOpts { - recursive?: boolean - mode?: number -} - -export interface RmOpts { - recursive?: boolean - force?: boolean -} - -export interface ReaddirOpts { - withFileTypes?: boolean -} - -export interface CopyFileOpts { - /** Preserve mode, owner, and timestamps where possible (cp -p). */ - preserveMetadata?: boolean -} - -// realpath's readlink walk is bounded so a symlink cycle the server didn't -// already reject with ELOOP can't spin forever on the client. -const REALPATH_MAX_HOPS = 40 - -/** - * A per-sandbox file system. Methods mirror `node:fs/promises`. Get an instance - * via `sandbox.fs()`. - */ -export class FileSystem { - /** The sandbox this file system is bound to. */ - readonly sandboxId: string - private readonly client: SandboxClient - - /** @internal Constructed by `Sandbox.fs`. */ - constructor(opts: {client: SandboxClient; sandboxId: string}) { - this.client = opts.client - this.sandboxId = opts.sandboxId - } - - private ref() { - return {sandbox: {selector: {case: 'id' as const, value: this.sandboxId}}} - } - - // ─── File I/O ───────────────────────────────────────────────────────── - - /** - * Read an entire file. Returns a Buffer, or a string when `opts.encoding` is - * set. Backed by the streaming ReadFile method; the chunks are concatenated - * here. For very large files, prefer streaming the chunks yourself. - */ - async readFile(path: string, opts?: ReadFileOpts): Promise { - const chunks: Uint8Array[] = [] - try { - for await (const chunk of this.client.rpc.readFile({...this.ref(), path})) { - if (chunk.data.length > 0) chunks.push(chunk.data) - } - } catch (err) { - throw toFileSystemError(err, 'open', path) - } - const buf = Buffer.concat(chunks) - return opts?.encoding ? buf.toString(opts.encoding) : buf - } - - /** - * Write `data` to `path`, replacing any existing contents. With - * `opts.recursive`, missing parent directories are created first. - */ - async writeFile(path: string, data: Buffer | Uint8Array | string, opts?: WriteFileOpts): Promise { - await this.writeStream(path, data, {mode: opts?.mode, recursive: opts?.recursive, append: false}, 'write') - } - - /** - * Append `data` to `path`, creating it if needed. Composed from WriteFile - * with append semantics; there is no dedicated append method. - */ - async appendFile(path: string, data: Buffer | Uint8Array | string, opts?: WriteFileOpts): Promise { - await this.writeStream(path, data, {mode: opts?.mode, recursive: opts?.recursive, append: true}, 'open') - } - - private async writeStream( - path: string, - data: Buffer | Uint8Array | string, - init: {mode?: number; recursive?: boolean; append: boolean}, - syscall: string, - ): Promise { - if (init.mode !== undefined) assertValidMode(init.mode, syscall, path) - const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : new Uint8Array(data) - const ref = this.ref() - async function* requestStream() { - yield create(WriteFileRequestSchema, { - input: { - case: 'init', - value: { - sandbox: ref.sandbox, - path, - mode: init.mode, - append: init.append, - createDirectories: init.recursive, - }, - }, - }) - // Split the payload into frames that stay under the gRPC message ceiling. - // subarray shares the backing buffer, so a small write still allocates - // nothing extra; an empty write sends no data frame at all (tee creates - // the empty file from the init message alone). - for (let offset = 0; offset < bytes.byteLength; offset += WRITE_CHUNK_BYTES) { - yield create(WriteFileRequestSchema, { - input: {case: 'data', value: bytes.subarray(offset, offset + WRITE_CHUNK_BYTES)}, - }) - } - } - try { - await this.client.rpc.writeFile(requestStream()) - } catch (err) { - throw toFileSystemError(err, syscall, path) - } - } - - // ─── Directory ops ────────────────────────────────────────────────────── - - async mkdir(path: string, opts?: MkdirOpts): Promise { - if (opts?.mode !== undefined) assertValidMode(opts.mode, 'mkdir', path) - try { - await this.client.rpc.mkdir({...this.ref(), path, recursive: opts?.recursive, mode: opts?.mode}) - } catch (err) { - throw toFileSystemError(err, 'mkdir', path) - } - } - - /** - * List directory entries. Returns `string[]` by default, or `DirEntry[]` when - * `opts.withFileTypes` is set. - */ - async readdir(path: string, opts?: ReaddirOpts): Promise { - let response - try { - response = await this.client.rpc.readDir({...this.ref(), path, withFileTypes: opts?.withFileTypes}) - } catch (err) { - throw toFileSystemError(err, 'scandir', path) - } - if (opts?.withFileTypes) { - return response.entries.map((e) => new DirEntry({name: e.name, type: fileTypeName(e.type)})) - } - return response.entries.map((e) => e.name) - } - - /** - * Create a uniquely named temporary directory. `prefix` is suffixed with a - * random string. Composed from mkdir with a retry on a name collision; there - * is no dedicated method. - */ - async mkdtemp(prefix: string): Promise { - for (let attempt = 0; attempt < 8; attempt++) { - const suffix = Math.random().toString(36).slice(2, 8) - const path = `${prefix}${suffix}` - try { - await this.client.rpc.mkdir({...this.ref(), path}) - return path - } catch (err) { - const fsErr = toFileSystemError(err, 'mkdir', path) - // Retry only on a name collision; anything else is a real failure. - if (fsErr.code !== 'EEXIST') throw fsErr - } - } - throw toFileSystemError(new Error('mkdtemp exhausted its retries'), 'mkdir', prefix) - } - - // ─── Stat family ────────────────────────────────────────────────────── - - /** Stat, following symlinks. */ - stat(path: string): Promise { - return this.statImpl(path, true) - } - - /** Lstat β€” stat the link itself rather than its target. */ - lstat(path: string): Promise { - return this.statImpl(path, false) - } - - private async statImpl(path: string, followSymlinks: boolean): Promise { - let response - try { - response = await this.client.rpc.stat({...this.ref(), path, followSymlinks}) - } catch (err) { - throw toFileSystemError(err, followSymlinks ? 'stat' : 'lstat', path) - } - return new StatResult({ - path: response.path, - size: Number(response.size), - mode: response.mode, - uname: response.uname, - gname: response.gname, - mtime: new Date(Number(response.mtimeUnixSeconds) * 1000), - type: fileTypeName(response.type), - }) - } - - /** - * Resolve `path` to its canonical form by walking symlinks. Composed from - * lstat + readlink with a hop limit, since the server exposes no one-shot - * realpath; the native backing can optimize this behind the unchanged - * surface later. Pathological per-component resolution (a symlink in the - * middle of a path) is not handled here yet β€” see the SDK reference. - */ - async realpath(path: string): Promise { - let current = path - for (let hop = 0; hop < REALPATH_MAX_HOPS; hop++) { - let info: StatResult - try { - info = await this.lstat(current) - } catch (err) { - // A missing or unreadable component surfaces as the underlying lstat - // error. Pass an already-classified FileSystemError through unchanged: - // Node's realpath likewise reports the underlying lstat syscall (an - // ENOENT here carries syscall 'lstat'), so preserving it keeps parity. - // Only an unclassified error gets wrapped under the realpath syscall. - if (err instanceof Error && 'code' in err) throw err - throw toFileSystemError(err, 'realpath', path) - } - if (info.type !== 'symlink') return current - const target = await this.readlink(current) - // An absolute target replaces the path; a relative one resolves against - // the link's directory. - current = target.startsWith('/') ? target : joinPath(dirname(current), target) - } - // Hit the hop limit: the chain is circular or pathologically deep. Node - // reports this as ELOOP, so build the code directly rather than routing a - // plain Error through toFileSystemError (which would classify as OTHER). - throw new FileSystemError({ - code: 'ELOOP', - syscall: 'realpath', - path, - message: `ELOOP: too many symbolic links, realpath '${path}'`, - }) - } - - // ─── Mutation ─────────────────────────────────────────────────────────── - - /** Remove a file or symlink. */ - unlink(path: string): Promise { - return this.removeImpl(path, {recursive: false, force: false}, 'unlink') - } - - /** Remove a path, optionally recursively and/or ignoring a missing target. */ - rm(path: string, opts?: RmOpts): Promise { - return this.removeImpl(path, {recursive: opts?.recursive ?? false, force: opts?.force ?? false}, 'unlink') - } - - /** Remove an empty directory. Fails with ENOTEMPTY if it isn't empty. */ - rmdir(path: string): Promise { - return this.removeImpl(path, {recursive: false, force: false}, 'rmdir') - } - - private async removeImpl(path: string, opts: {recursive: boolean; force: boolean}, syscall: string): Promise { - try { - await this.client.rpc.remove({...this.ref(), path, recursive: opts.recursive, ignoreMissing: opts.force}) - } catch (err) { - throw toFileSystemError(err, syscall, path) - } - } - - async rename(from: string, to: string): Promise { - try { - await this.client.rpc.rename({...this.ref(), fromPath: from, toPath: to}) - } catch (err) { - throw toFileSystemError(err, 'rename', from) - } - } - - async copyFile(from: string, to: string, opts?: CopyFileOpts): Promise { - try { - await this.client.rpc.copyFile({ - ...this.ref(), - fromPath: from, - toPath: to, - preserveMetadata: opts?.preserveMetadata, - }) - } catch (err) { - throw toFileSystemError(err, 'copyfile', from) - } - } - - async truncate(path: string, size = 0): Promise { - if (!Number.isInteger(size) || size < 0) { - throw new FileSystemError({ - code: 'EINVAL', - syscall: 'ftruncate', - path, - message: `ftruncate '${path}': size must be a non-negative integer`, - }) - } - try { - await this.client.rpc.truncate({...this.ref(), path, size: BigInt(size)}) - } catch (err) { - throw toFileSystemError(err, 'ftruncate', path) - } - } - - /** chmod. `mode` accepts an octal integer (`0o755`) or an octal string (`"755"`). */ - async chmod(path: string, mode: number | string): Promise { - const numeric = typeof mode === 'string' ? parseInt(mode, 8) : mode - assertValidMode(numeric, 'chmod', path) - try { - await this.client.rpc.chmod({...this.ref(), path, mode: numeric}) - } catch (err) { - throw toFileSystemError(err, 'chmod', path) - } - } - - async chown(path: string, uid: number, gid: number): Promise { - try { - await this.client.rpc.chown({...this.ref(), path, uid, gid}) - } catch (err) { - throw toFileSystemError(err, 'chown', path) - } - } - - // ─── Symlinks ───────────────────────────────────────────────────────── - - async symlink(target: string, linkPath: string): Promise { - try { - await this.client.rpc.symlink({...this.ref(), target, linkPath}) - } catch (err) { - throw toFileSystemError(err, 'symlink', linkPath) - } - } - - async readlink(path: string): Promise { - try { - const response = await this.client.rpc.readlink({...this.ref(), path}) - return response.target - } catch (err) { - throw toFileSystemError(err, 'readlink', path) - } - } - - // ─── Access / probe ───────────────────────────────────────────────────── - - /** Check access to `path`. Resolves on success, rejects with a FileSystemError otherwise. */ - async access(path: string, mode?: number): Promise { - try { - await this.client.rpc.access({...this.ref(), path, mode}) - } catch (err) { - throw toFileSystemError(err, 'access', path) - } - } - - /** - * Whether `path` exists. Composed from stat + catching ENOENT. Not part of - * `node:fs/promises`, but an SDK convenience callers ask for. Errors other - * than ENOENT (for example EACCES on an unreadable parent) propagate. - */ - async exists(path: string): Promise { - try { - await this.stat(path) - return true - } catch (err) { - if (err instanceof Error && 'code' in err && (err as {code: unknown}).code === 'ENOENT') return false - throw err - } - } -} - -// ─── Path helpers ───────────────────────────────────────────────────────── -// Minimal POSIX path helpers for the realpath walk; the SDK doesn't pull in -// node:path so it stays usable in non-Node runtimes. - -function dirname(path: string): string { - const idx = path.lastIndexOf('/') - if (idx < 0) return '.' - if (idx === 0) return '/' - return path.slice(0, idx) -} - -function joinPath(dir: string, name: string): string { - if (dir === '/') return `/${name}` - return `${dir}/${name}` -} diff --git a/packages/sandbox/src/gen/depot/sandbox/v1/command_pb.ts b/packages/sandbox/src/gen/depot/sandbox/v1/command_pb.ts deleted file mode 100644 index 0c99c8c..0000000 --- a/packages/sandbox/src/gen/depot/sandbox/v1/command_pb.ts +++ /dev/null @@ -1,523 +0,0 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts,import_extension=js" -// @generated from file depot/sandbox/v1/command.proto (package depot.sandbox.v1, syntax proto3) -/* eslint-disable */ - -import type {Message} from '@bufbuild/protobuf' -import type {GenEnum, GenFile, GenMessage} from '@bufbuild/protobuf/codegenv2' -import {enumDesc, fileDesc, messageDesc} from '@bufbuild/protobuf/codegenv2' -import type {Timestamp} from '@bufbuild/protobuf/wkt' -import {file_google_protobuf_timestamp} from '@bufbuild/protobuf/wkt' -import type {SandboxRef} from './refs_pb.js' -import {file_depot_sandbox_v1_refs} from './refs_pb.js' - -/** - * Describes the file depot/sandbox/v1/command.proto. - */ -export const file_depot_sandbox_v1_command: GenFile = /*@__PURE__*/ fileDesc( - 'Ch5kZXBvdC9zYW5kYm94L3YxL2NvbW1hbmQucHJvdG8SEGRlcG90LnNhbmRib3gudjEimAQKF1NhbmRib3hDb21tYW5kRXhlY3V0aW9uEg4KBmNtZF9pZBgBIAEoCRISCgpzYW5kYm94X2lkGAIgASgJEgsKA2NtZBgDIAEoCRIMCgRhcmdzGAQgAygJEhAKA2N3ZBgFIAEoCUgAiAEBEj8KA2VudhgGIAMoCzIyLmRlcG90LnNhbmRib3gudjEuU2FuZGJveENvbW1hbmRFeGVjdXRpb24uRW52RW50cnkSDAoEc3VkbxgHIAEoCBIQCghkZXRhY2hlZBgIIAEoCBI/CgZzdGF0dXMYCSABKA4yLy5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hDb21tYW5kRXhlY3V0aW9uU3RhdHVzEi4KCnN0YXJ0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjQKC2ZpbmlzaGVkX2F0GAsgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgBiAEBEhYKCWV4aXRfY29kZRgMIAEoBUgCiAEBEhwKFHN0ZG91dF9ieXRlc19lbWl0dGVkGA0gASgDEhwKFHN0ZGVycl9ieXRlc19lbWl0dGVkGA4gASgDGioKCEVudkVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAFCBgoEX2N3ZEIOCgxfZmluaXNoZWRfYXRCDAoKX2V4aXRfY29kZSK8BwocU2FuZGJveENvbW1hbmRFeGVjdXRpb25FdmVudBJJCgdzdGFydGVkGAEgASgLMjYuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94Q29tbWFuZEV4ZWN1dGlvbkV2ZW50LlN0YXJ0ZWRIABJMCgZzdGRvdXQYAiABKAsyOi5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hDb21tYW5kRXhlY3V0aW9uRXZlbnQuU3Rkb3V0Qnl0ZXNIABJMCgZzdGRlcnIYAyABKAsyOi5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hDb21tYW5kRXhlY3V0aW9uRXZlbnQuU3RkZXJyQnl0ZXNIABJLCghmaW5pc2hlZBgEIAEoCzI3LmRlcG90LnNhbmRib3gudjEuU2FuZGJveENvbW1hbmRFeGVjdXRpb25FdmVudC5GaW5pc2hlZEgAEkUKBWVycm9yGAUgASgLMjQuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94Q29tbWFuZEV4ZWN1dGlvbkV2ZW50LkVycm9ySAASUgoHZXZpY3RlZBgGIAEoCzI/LmRlcG90LnNhbmRib3gudjEuU2FuZGJveENvbW1hbmRFeGVjdXRpb25FdmVudC5FdmljdGVkRWFybHlEYXRhSAAaSQoHU3RhcnRlZBIOCgZjbWRfaWQYASABKAkSLgoKc3RhcnRlZF9hdBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAaXwoLU3Rkb3V0Qnl0ZXMSDAoEZGF0YRgBIAEoDBITCgtieXRlX29mZnNldBgCIAEoAxItCgl0aW1lc3RhbXAYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wGl8KC1N0ZGVyckJ5dGVzEgwKBGRhdGEYASABKAwSEwoLYnl0ZV9vZmZzZXQYAiABKAMSLQoJdGltZXN0YW1wGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBpOCghGaW5pc2hlZBIRCglleGl0X2NvZGUYASABKAUSLwoLZmluaXNoZWRfYXQYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wGhcKBUVycm9yEg4KBnJlYXNvbhgBIAEoCRpOChBFdmljdGVkRWFybHlEYXRhEhwKFGRyb3BwZWRfYnl0ZXNfc3Rkb3V0GAEgASgDEhwKFGRyb3BwZWRfYnl0ZXNfc3RkZXJyGAIgASgDQgcKBWV2ZW50Ip4CChFSdW5Db21tYW5kUmVxdWVzdBItCgdzYW5kYm94GAEgASgLMhwuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94UmVmEgsKA2NtZBgCIAEoCRIMCgRhcmdzGAMgAygJEhAKA2N3ZBgEIAEoCUgAiAEBEjkKA2VudhgFIAMoCzIsLmRlcG90LnNhbmRib3gudjEuUnVuQ29tbWFuZFJlcXVlc3QuRW52RW50cnkSEQoEc3VkbxgGIAEoCEgBiAEBEhUKCGRldGFjaGVkGAcgASgISAKIAQEaKgoIRW52RW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUIGCgRfY3dkQgcKBV9zdWRvQgsKCV9kZXRhY2hlZCq2AgodU2FuZGJveENvbW1hbmRFeGVjdXRpb25TdGF0dXMSMAosU0FOREJPWF9DT01NQU5EX0VYRUNVVElPTl9TVEFUVVNfVU5TUEVDSUZJRUQQABIsCihTQU5EQk9YX0NPTU1BTkRfRVhFQ1VUSU9OX1NUQVRVU19QRU5ESU5HEAESLAooU0FOREJPWF9DT01NQU5EX0VYRUNVVElPTl9TVEFUVVNfUlVOTklORxACEi0KKVNBTkRCT1hfQ09NTUFORF9FWEVDVVRJT05fU1RBVFVTX0ZJTklTSEVEEAMSKwonU0FOREJPWF9DT01NQU5EX0VYRUNVVElPTl9TVEFUVVNfRkFJTEVEEAQSKwonU0FOREJPWF9DT01NQU5EX0VYRUNVVElPTl9TVEFUVVNfS0lMTEVEEAViBnByb3RvMw', - [file_depot_sandbox_v1_refs, file_google_protobuf_timestamp], -) - -/** - * A record of a single command run inside a sandbox, including how it was - * invoked and its current state. Returned by the command lookup methods. - * - * @generated from message depot.sandbox.v1.SandboxCommandExecution - */ -export type SandboxCommandExecution = Message<'depot.sandbox.v1.SandboxCommandExecution'> & { - /** - * Unique identifier for this command execution, assigned by the server. - * - * @generated from field: string cmd_id = 1; - */ - cmdId: string - - /** - * Unique identifier for the sandbox this command ran in. - * - * @generated from field: string sandbox_id = 2; - */ - sandboxId: string - - /** - * The executable that was run. - * - * @generated from field: string cmd = 3; - */ - cmd: string - - /** - * Positional arguments passed to the command. Empty when none were supplied. - * - * @generated from field: repeated string args = 4; - */ - args: string[] - - /** - * Working directory the command ran in. Unset when the caller did not request one. - * - * @generated from field: optional string cwd = 5; - */ - cwd?: string - - /** - * Environment the command ran with. This is the merge of the sandbox-level - * environment set when the sandbox was created and the environment supplied - * with the command, with the per-command values taking precedence on any - * key that appears in both. - * - * @generated from field: map env = 6; - */ - env: {[key: string]: string} - - /** - * Whether the command was run through `sudo -E`. - * - * @generated from field: bool sudo = 7; - */ - sudo: boolean - - /** - * Whether the command was run detached. Detached mode is in beta: the command - * runs fire-and-forget and its output is not retained, so it can't be - * retrieved through this API yet. - * - * @generated from field: bool detached = 8; - */ - detached: boolean - - /** - * Current state of the command. This is independent of the exit code: a - * command that exits with a nonzero code still reports a finished status, - * which is the normal outcome for a failed program. - * - * @generated from field: depot.sandbox.v1.SandboxCommandExecutionStatus status = 9; - */ - status: SandboxCommandExecutionStatus - - /** - * Time the command started, in RFC 3339 format. - * - * @generated from field: google.protobuf.Timestamp started_at = 10; - */ - startedAt?: Timestamp - - /** - * Time the command reached a terminal state, in RFC 3339 format. Unset until - * the command has finished, failed, or been killed. - * - * @generated from field: optional google.protobuf.Timestamp finished_at = 11; - */ - finishedAt?: Timestamp - - /** - * Exit code the process returned. Set when the process exited on its own; - * unset when the command failed to start or was killed before producing one. - * - * @generated from field: optional int32 exit_code = 12; - */ - exitCode?: number - - /** - * Total number of stdout bytes emitted so far. Tracked separately from - * stderr so callers can follow each stream's byte offset independently. - * - * @generated from field: int64 stdout_bytes_emitted = 13; - */ - stdoutBytesEmitted: bigint - - /** - * Total number of stderr bytes emitted so far. Tracked separately from - * stdout so callers can follow each stream's byte offset independently. - * - * @generated from field: int64 stderr_bytes_emitted = 14; - */ - stderrBytesEmitted: bigint -} - -/** - * Describes the message depot.sandbox.v1.SandboxCommandExecution. - * Use `create(SandboxCommandExecutionSchema)` to create a new message. - */ -export const SandboxCommandExecutionSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_command, - 0, -) - -/** - * A single event in the stream a command produces while it runs. Each event is - * one of: the command starting, a chunk of stdout or stderr, the command - * finishing, a recoverable error, or a notice that earlier output was dropped. - * Output chunks are raw bytes; callers decode to text themselves. - * - * @generated from message depot.sandbox.v1.SandboxCommandExecutionEvent - */ -export type SandboxCommandExecutionEvent = Message<'depot.sandbox.v1.SandboxCommandExecutionEvent'> & { - /** - * @generated from oneof depot.sandbox.v1.SandboxCommandExecutionEvent.event - */ - event: - | { - /** - * @generated from field: depot.sandbox.v1.SandboxCommandExecutionEvent.Started started = 1; - */ - value: SandboxCommandExecutionEvent_Started - case: 'started' - } - | { - /** - * @generated from field: depot.sandbox.v1.SandboxCommandExecutionEvent.StdoutBytes stdout = 2; - */ - value: SandboxCommandExecutionEvent_StdoutBytes - case: 'stdout' - } - | { - /** - * @generated from field: depot.sandbox.v1.SandboxCommandExecutionEvent.StderrBytes stderr = 3; - */ - value: SandboxCommandExecutionEvent_StderrBytes - case: 'stderr' - } - | { - /** - * @generated from field: depot.sandbox.v1.SandboxCommandExecutionEvent.Finished finished = 4; - */ - value: SandboxCommandExecutionEvent_Finished - case: 'finished' - } - | { - /** - * @generated from field: depot.sandbox.v1.SandboxCommandExecutionEvent.Error error = 5; - */ - value: SandboxCommandExecutionEvent_Error - case: 'error' - } - | { - /** - * @generated from field: depot.sandbox.v1.SandboxCommandExecutionEvent.EvictedEarlyData evicted = 6; - */ - value: SandboxCommandExecutionEvent_EvictedEarlyData - case: 'evicted' - } - | {case: undefined; value?: undefined} -} - -/** - * Describes the message depot.sandbox.v1.SandboxCommandExecutionEvent. - * Use `create(SandboxCommandExecutionEventSchema)` to create a new message. - */ -export const SandboxCommandExecutionEventSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_command, - 1, -) - -/** - * The first event on every stream, signaling that the command has started. - * - * @generated from message depot.sandbox.v1.SandboxCommandExecutionEvent.Started - */ -export type SandboxCommandExecutionEvent_Started = Message<'depot.sandbox.v1.SandboxCommandExecutionEvent.Started'> & { - /** - * Unique identifier for this command execution, assigned by the server and - * echoed here so the caller can correlate the stream with the command. - * - * @generated from field: string cmd_id = 1; - */ - cmdId: string - - /** - * Time the command started, in RFC 3339 format. - * - * @generated from field: google.protobuf.Timestamp started_at = 2; - */ - startedAt?: Timestamp -} - -/** - * Describes the message depot.sandbox.v1.SandboxCommandExecutionEvent.Started. - * Use `create(SandboxCommandExecutionEvent_StartedSchema)` to create a new message. - */ -export const SandboxCommandExecutionEvent_StartedSchema: GenMessage = - /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_command, 1, 0) - -/** - * A chunk of standard output produced by the command. - * - * @generated from message depot.sandbox.v1.SandboxCommandExecutionEvent.StdoutBytes - */ -export type SandboxCommandExecutionEvent_StdoutBytes = - Message<'depot.sandbox.v1.SandboxCommandExecutionEvent.StdoutBytes'> & { - /** - * Raw stdout bytes. The contents are not guaranteed to be valid UTF-8. - * - * @generated from field: bytes data = 1; - */ - data: Uint8Array - - /** - * Total number of stdout bytes emitted through the end of this chunk. - * - * @generated from field: int64 byte_offset = 2; - */ - byteOffset: bigint - - /** - * Time this chunk was emitted, in RFC 3339 format. - * - * @generated from field: google.protobuf.Timestamp timestamp = 3; - */ - timestamp?: Timestamp - } - -/** - * Describes the message depot.sandbox.v1.SandboxCommandExecutionEvent.StdoutBytes. - * Use `create(SandboxCommandExecutionEvent_StdoutBytesSchema)` to create a new message. - */ -export const SandboxCommandExecutionEvent_StdoutBytesSchema: GenMessage = - /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_command, 1, 1) - -/** - * A chunk of standard error produced by the command. - * - * @generated from message depot.sandbox.v1.SandboxCommandExecutionEvent.StderrBytes - */ -export type SandboxCommandExecutionEvent_StderrBytes = - Message<'depot.sandbox.v1.SandboxCommandExecutionEvent.StderrBytes'> & { - /** - * Raw stderr bytes. The contents are not guaranteed to be valid UTF-8. - * - * @generated from field: bytes data = 1; - */ - data: Uint8Array - - /** - * Total number of stderr bytes emitted through the end of this chunk. - * - * @generated from field: int64 byte_offset = 2; - */ - byteOffset: bigint - - /** - * Time this chunk was emitted, in RFC 3339 format. - * - * @generated from field: google.protobuf.Timestamp timestamp = 3; - */ - timestamp?: Timestamp - } - -/** - * Describes the message depot.sandbox.v1.SandboxCommandExecutionEvent.StderrBytes. - * Use `create(SandboxCommandExecutionEvent_StderrBytesSchema)` to create a new message. - */ -export const SandboxCommandExecutionEvent_StderrBytesSchema: GenMessage = - /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_command, 1, 2) - -/** - * The terminal event when a command runs to completion, signaling that the - * process exited. - * - * @generated from message depot.sandbox.v1.SandboxCommandExecutionEvent.Finished - */ -export type SandboxCommandExecutionEvent_Finished = - Message<'depot.sandbox.v1.SandboxCommandExecutionEvent.Finished'> & { - /** - * Exit code the process returned. On POSIX systems this is 0 through 255, - * where 0 indicates success. - * - * @generated from field: int32 exit_code = 1; - */ - exitCode: number - - /** - * Time the process exited, in RFC 3339 format. - * - * @generated from field: google.protobuf.Timestamp finished_at = 2; - */ - finishedAt?: Timestamp - } - -/** - * Describes the message depot.sandbox.v1.SandboxCommandExecutionEvent.Finished. - * Use `create(SandboxCommandExecutionEvent_FinishedSchema)` to create a new message. - */ -export const SandboxCommandExecutionEvent_FinishedSchema: GenMessage = - /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_command, 1, 3) - -/** - * A recoverable failure that does not end the stream, for example a single - * output chunk being lost while more output is still to come. Fatal errors - * are reported through the usual error channel rather than this event. - * - * @generated from message depot.sandbox.v1.SandboxCommandExecutionEvent.Error - */ -export type SandboxCommandExecutionEvent_Error = Message<'depot.sandbox.v1.SandboxCommandExecutionEvent.Error'> & { - /** - * Human-readable description of what went wrong. - * - * @generated from field: string reason = 1; - */ - reason: string -} - -/** - * Describes the message depot.sandbox.v1.SandboxCommandExecutionEvent.Error. - * Use `create(SandboxCommandExecutionEvent_ErrorSchema)` to create a new message. - */ -export const SandboxCommandExecutionEvent_ErrorSchema: GenMessage = - /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_command, 1, 4) - -/** - * A notice that some output was dropped before this consumer began reading, - * which can happen when reattaching to a command that is already running. - * The byte counts let the consumer account for the gap. - * - * @generated from message depot.sandbox.v1.SandboxCommandExecutionEvent.EvictedEarlyData - */ -export type SandboxCommandExecutionEvent_EvictedEarlyData = - Message<'depot.sandbox.v1.SandboxCommandExecutionEvent.EvictedEarlyData'> & { - /** - * Number of stdout bytes dropped before this consumer began reading. - * - * @generated from field: int64 dropped_bytes_stdout = 1; - */ - droppedBytesStdout: bigint - - /** - * Number of stderr bytes dropped before this consumer began reading. - * - * @generated from field: int64 dropped_bytes_stderr = 2; - */ - droppedBytesStderr: bigint - } - -/** - * Describes the message depot.sandbox.v1.SandboxCommandExecutionEvent.EvictedEarlyData. - * Use `create(SandboxCommandExecutionEvent_EvictedEarlyDataSchema)` to create a new message. - */ -export const SandboxCommandExecutionEvent_EvictedEarlyDataSchema: GenMessage = - /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_command, 1, 5) - -/** - * Request to run a command in a sandbox. The `RunCommand` method streams back a - * sequence of events that begins with a started event and ends with a finished - * event, or terminates with an error. - * - * @generated from message depot.sandbox.v1.RunCommandRequest - */ -export type RunCommandRequest = Message<'depot.sandbox.v1.RunCommandRequest'> & { - /** - * The sandbox to run the command in. - * - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * The executable to run. Looked up on the sandbox's PATH unless given as an - * absolute path. - * - * @generated from field: string cmd = 2; - */ - cmd: string - - /** - * Positional arguments to pass to the command. Empty by default. - * - * @generated from field: repeated string args = 3; - */ - args: string[] - - /** - * Working directory to run the command in. Default: the sandbox's default - * working directory. - * - * @generated from field: optional string cwd = 4; - */ - cwd?: string - - /** - * Environment variables for this command. These are merged with the - * sandbox-level environment set when the sandbox was created, and take - * precedence on any key that appears in both. Keys must be valid POSIX - * environment variable names, matching [A-Za-z_][A-Za-z0-9_]*. - * - * @generated from field: map env = 5; - */ - env: {[key: string]: string} - - /** - * Run the command through `sudo -E`, preserving the merged environment. - * Default: false. - * - * @generated from field: optional bool sudo = 6; - */ - sudo?: boolean - - /** - * Run the command detached (beta). The server returns once the command has - * started and the command keeps running in the sandbox, but its output is not - * retained and can't be retrieved yet, so the response stream ends right after - * the Started event. Reattaching to a detached command's output is a future - * API. Default: false. - * - * @generated from field: optional bool detached = 7; - */ - detached?: boolean -} - -/** - * Describes the message depot.sandbox.v1.RunCommandRequest. - * Use `create(RunCommandRequestSchema)` to create a new message. - */ -export const RunCommandRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_command, - 2, -) - -/** - * The lifecycle stage a command has reached. A command starts pending, begins - * running once the process is launched, and ends in one of the terminal states: - * finished if the process exited, failed if it could not start or the guest - * reported an error, or killed if it was signaled while running. - * - * @generated from enum depot.sandbox.v1.SandboxCommandExecutionStatus - */ -export enum SandboxCommandExecutionStatus { - /** - * @generated from enum value: SANDBOX_COMMAND_EXECUTION_STATUS_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: SANDBOX_COMMAND_EXECUTION_STATUS_PENDING = 1; - */ - PENDING = 1, - - /** - * @generated from enum value: SANDBOX_COMMAND_EXECUTION_STATUS_RUNNING = 2; - */ - RUNNING = 2, - - /** - * @generated from enum value: SANDBOX_COMMAND_EXECUTION_STATUS_FINISHED = 3; - */ - FINISHED = 3, - - /** - * @generated from enum value: SANDBOX_COMMAND_EXECUTION_STATUS_FAILED = 4; - */ - FAILED = 4, - - /** - * @generated from enum value: SANDBOX_COMMAND_EXECUTION_STATUS_KILLED = 5; - */ - KILLED = 5, -} - -/** - * Describes the enum depot.sandbox.v1.SandboxCommandExecutionStatus. - */ -export const SandboxCommandExecutionStatusSchema: GenEnum = /*@__PURE__*/ enumDesc( - file_depot_sandbox_v1_command, - 0, -) diff --git a/packages/sandbox/src/gen/depot/sandbox/v1/filesystem_pb.ts b/packages/sandbox/src/gen/depot/sandbox/v1/filesystem_pb.ts deleted file mode 100644 index 748b6df..0000000 --- a/packages/sandbox/src/gen/depot/sandbox/v1/filesystem_pb.ts +++ /dev/null @@ -1,1059 +0,0 @@ -// File system operations against a sandbox. -// -// The server may back these operations with different implementations, but the -// message surface is the same regardless of which one is active. -// -// Errors are returned as Connect errors with a FileSystemErrorDetail attached. -// The SDK converts that detail into a Node-style Error carrying code, syscall, -// path, and errno, so callers can keep writing code against the shape of -// node:fs/promises. -// -// The methods themselves are declared on the sandbox service; this file only -// defines the messages and enums they use. - -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts,import_extension=js" -// @generated from file depot/sandbox/v1/filesystem.proto (package depot.sandbox.v1, syntax proto3) -/* eslint-disable */ - -import type {Message} from '@bufbuild/protobuf' -import type {GenEnum, GenFile, GenMessage} from '@bufbuild/protobuf/codegenv2' -import {enumDesc, fileDesc, messageDesc} from '@bufbuild/protobuf/codegenv2' -import type {SandboxRef} from './refs_pb.js' -import {file_depot_sandbox_v1_refs} from './refs_pb.js' - -/** - * Describes the file depot/sandbox/v1/filesystem.proto. - */ -export const file_depot_sandbox_v1_filesystem: GenFile = /*@__PURE__*/ fileDesc( - 'CiFkZXBvdC9zYW5kYm94L3YxL2ZpbGVzeXN0ZW0ucHJvdG8SEGRlcG90LnNhbmRib3gudjEijQEKFUZpbGVTeXN0ZW1FcnJvckRldGFpbBIzCgRjb2RlGAEgASgOMiUuZGVwb3Quc2FuZGJveC52MS5GaWxlU3lzdGVtRXJyb3JDb2RlEg8KB3N5c2NhbGwYAiABKAkSDAoEcGF0aBgDIAEoCRIUCgdtZXNzYWdlGAQgASgJSACIAQFCCgoIX21lc3NhZ2UijQEKDE1rZGlyUmVxdWVzdBItCgdzYW5kYm94GAEgASgLMhwuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94UmVmEgwKBHBhdGgYAiABKAkSFgoJcmVjdXJzaXZlGAMgASgISACIAQESEQoEbW9kZRgEIAEoDUgBiAEBQgwKCl9yZWN1cnNpdmVCBwoFX21vZGUiDwoNTWtkaXJSZXNwb25zZSJ8CgtTdGF0UmVxdWVzdBItCgdzYW5kYm94GAEgASgLMhwuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94UmVmEgwKBHBhdGgYAiABKAkSHAoPZm9sbG93X3N5bWxpbmtzGAMgASgISACIAQFCEgoQX2ZvbGxvd19zeW1saW5rcyKcAQoMU3RhdFJlc3BvbnNlEgwKBHBhdGgYASABKAkSDAoEc2l6ZRgCIAEoAxIMCgRtb2RlGAMgASgNEigKBHR5cGUYBCABKA4yGi5kZXBvdC5zYW5kYm94LnYxLkZpbGVUeXBlEg0KBXVuYW1lGAUgASgJEg0KBWduYW1lGAYgASgJEhoKEm10aW1lX3VuaXhfc2Vjb25kcxgHIAEoAyJ/Cg5SZWFkRGlyUmVxdWVzdBItCgdzYW5kYm94GAEgASgLMhwuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94UmVmEgwKBHBhdGgYAiABKAkSHAoPd2l0aF9maWxlX3R5cGVzGAMgASgISACIAQFCEgoQX3dpdGhfZmlsZV90eXBlcyI+Cg9SZWFkRGlyUmVzcG9uc2USKwoHZW50cmllcxgBIAMoCzIaLmRlcG90LnNhbmRib3gudjEuRGlyRW50cnkiQgoIRGlyRW50cnkSDAoEbmFtZRgBIAEoCRIoCgR0eXBlGAIgASgOMhouZGVwb3Quc2FuZGJveC52MS5GaWxlVHlwZSKiAQoNUmVtb3ZlUmVxdWVzdBItCgdzYW5kYm94GAEgASgLMhwuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94UmVmEgwKBHBhdGgYAiABKAkSFgoJcmVjdXJzaXZlGAMgASgISACIAQESGwoOaWdub3JlX21pc3NpbmcYBCABKAhIAYgBAUIMCgpfcmVjdXJzaXZlQhEKD19pZ25vcmVfbWlzc2luZyIQCg5SZW1vdmVSZXNwb25zZSJiCg1SZW5hbWVSZXF1ZXN0Ei0KB3NhbmRib3gYASABKAsyHC5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hSZWYSEQoJZnJvbV9wYXRoGAIgASgJEg8KB3RvX3BhdGgYAyABKAkiEAoOUmVuYW1lUmVzcG9uc2UimgEKD0NvcHlGaWxlUmVxdWVzdBItCgdzYW5kYm94GAEgASgLMhwuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94UmVmEhEKCWZyb21fcGF0aBgCIAEoCRIPCgd0b19wYXRoGAMgASgJEh4KEXByZXNlcnZlX21ldGFkYXRhGAQgASgISACIAQFCFAoSX3ByZXNlcnZlX21ldGFkYXRhIhIKEENvcHlGaWxlUmVzcG9uc2UiXAoPVHJ1bmNhdGVSZXF1ZXN0Ei0KB3NhbmRib3gYASABKAsyHC5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hSZWYSDAoEcGF0aBgCIAEoCRIMCgRzaXplGAMgASgDIhIKEFRydW5jYXRlUmVzcG9uc2UiZwoMQ2htb2RSZXF1ZXN0Ei0KB3NhbmRib3gYASABKAsyHC5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hSZWYSDAoEcGF0aBgCIAEoCRIRCgRtb2RlGAMgASgNSACIAQFCBwoFX21vZGUiDwoNQ2htb2RSZXNwb25zZSJ/CgxDaG93blJlcXVlc3QSLQoHc2FuZGJveBgBIAEoCzIcLmRlcG90LnNhbmRib3gudjEuU2FuZGJveFJlZhIMCgRwYXRoGAIgASgJEhAKA3VpZBgDIAEoDUgAiAEBEhAKA2dpZBgEIAEoDUgBiAEBQgYKBF91aWRCBgoEX2dpZCIPCg1DaG93blJlc3BvbnNlImIKDlN5bWxpbmtSZXF1ZXN0Ei0KB3NhbmRib3gYASABKAsyHC5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hSZWYSDgoGdGFyZ2V0GAIgASgJEhEKCWxpbmtfcGF0aBgDIAEoCSIRCg9TeW1saW5rUmVzcG9uc2UiTgoPUmVhZGxpbmtSZXF1ZXN0Ei0KB3NhbmRib3gYASABKAsyHC5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hSZWYSDAoEcGF0aBgCIAEoCSIiChBSZWFkbGlua1Jlc3BvbnNlEg4KBnRhcmdldBgBIAEoCSJoCg1BY2Nlc3NSZXF1ZXN0Ei0KB3NhbmRib3gYASABKAsyHC5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hSZWYSDAoEcGF0aBgCIAEoCRIRCgRtb2RlGAMgASgNSACIAQFCBwoFX21vZGUiEAoOQWNjZXNzUmVzcG9uc2UiTgoPUmVhZEZpbGVSZXF1ZXN0Ei0KB3NhbmRib3gYASABKAsyHC5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3hSZWYSDAoEcGF0aBgCIAEoCSIzCglGaWxlQ2h1bmsSDAoEZGF0YRgBIAEoDBIQCgNlb2YYAiABKAhIAIgBAUIGCgRfZW9mIp4CChBXcml0ZUZpbGVSZXF1ZXN0EjcKBGluaXQYASABKAsyJy5kZXBvdC5zYW5kYm94LnYxLldyaXRlRmlsZVJlcXVlc3QuSW5pdEgAEg4KBGRhdGEYAiABKAxIABq3AQoESW5pdBItCgdzYW5kYm94GAEgASgLMhwuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94UmVmEgwKBHBhdGgYAiABKAkSEQoEbW9kZRgDIAEoDUgAiAEBEhMKBmFwcGVuZBgEIAEoCEgBiAEBEh8KEmNyZWF0ZV9kaXJlY3RvcmllcxgFIAEoCEgCiAEBQgcKBV9tb2RlQgkKB19hcHBlbmRCFQoTX2NyZWF0ZV9kaXJlY3Rvcmllc0IHCgVpbnB1dCIqChFXcml0ZUZpbGVSZXNwb25zZRIVCg1ieXRlc193cml0dGVuGAEgASgDKrQHChNGaWxlU3lzdGVtRXJyb3JDb2RlEiUKIUZJTEVTWVNURU1fRVJST1JfQ09ERV9VTlNQRUNJRklFRBAAEh8KG0ZJTEVTWVNURU1fRVJST1JfQ09ERV9FUEVSTRABEiAKHEZJTEVTWVNURU1fRVJST1JfQ09ERV9FTk9FTlQQAhIfChtGSUxFU1lTVEVNX0VSUk9SX0NPREVfRUlOVFIQBBIdChlGSUxFU1lTVEVNX0VSUk9SX0NPREVfRUlPEAUSHwobRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VCQURGEAkSIAocRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VOT01FTRAMEiAKHEZJTEVTWVNURU1fRVJST1JfQ09ERV9FQUNDRVMQDRIfChtGSUxFU1lTVEVNX0VSUk9SX0NPREVfRUJVU1kQEBIgChxGSUxFU1lTVEVNX0VSUk9SX0NPREVfRUVYSVNUEBESHwobRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VYREVWEBISIQodRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VOT1RESVIQFBIgChxGSUxFU1lTVEVNX0VSUk9SX0NPREVfRUlTRElSEBUSIAocRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VJTlZBTBAWEiAKHEZJTEVTWVNURU1fRVJST1JfQ09ERV9FTkZJTEUQFxIgChxGSUxFU1lTVEVNX0VSUk9SX0NPREVfRU1GSUxFEBgSIQodRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VUWFRCU1kQGhIfChtGSUxFU1lTVEVNX0VSUk9SX0NPREVfRUZCSUcQGxIgChxGSUxFU1lTVEVNX0VSUk9SX0NPREVfRU5PU1BDEBwSHwobRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VST0ZTEB4SIAocRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VNTElOSxAfEiYKIkZJTEVTWVNURU1fRVJST1JfQ09ERV9FTkFNRVRPT0xPTkcQJBIjCh9GSUxFU1lTVEVNX0VSUk9SX0NPREVfRU5PVEVNUFRZECcSHwobRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VMT09QECgSJAogRklMRVNZU1RFTV9FUlJPUl9DT0RFX0VPUE5PVFNVUFAQXxIgChxGSUxFU1lTVEVNX0VSUk9SX0NPREVfRURRVU9UEHoSIAobRklMRVNZU1RFTV9FUlJPUl9DT0RFX09USEVSEIAgKs8BCghGaWxlVHlwZRIZChVGSUxFX1RZUEVfVU5TUEVDSUZJRUQQABISCg5GSUxFX1RZUEVfRklMRRABEhcKE0ZJTEVfVFlQRV9ESVJFQ1RPUlkQAhIVChFGSUxFX1RZUEVfU1lNTElOSxADEhoKFkZJTEVfVFlQRV9CTE9DS19ERVZJQ0UQBBIeChpGSUxFX1RZUEVfQ0hBUkFDVEVSX0RFVklDRRAFEhIKDkZJTEVfVFlQRV9GSUZPEAYSFAoQRklMRV9UWVBFX1NPQ0tFVBAHYgZwcm90bzM', - [file_depot_sandbox_v1_refs], -) - -/** - * The error detail the server attaches to a Connect error's details. The SDK - * reads it back and uses it to build a Node-style FileSystemError. - * - * @generated from message depot.sandbox.v1.FileSystemErrorDetail - */ -export type FileSystemErrorDetail = Message<'depot.sandbox.v1.FileSystemErrorDetail'> & { - /** - * @generated from field: depot.sandbox.v1.FileSystemErrorCode code = 1; - */ - code: FileSystemErrorCode - - /** - * @generated from field: string syscall = 2; - */ - syscall: string - - /** - * @generated from field: string path = 3; - */ - path: string - - /** - * @generated from field: optional string message = 4; - */ - message?: string -} - -/** - * Describes the message depot.sandbox.v1.FileSystemErrorDetail. - * Use `create(FileSystemErrorDetailSchema)` to create a new message. - */ -export const FileSystemErrorDetailSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 0, -) - -/** - * @generated from message depot.sandbox.v1.MkdirRequest - */ -export type MkdirRequest = Message<'depot.sandbox.v1.MkdirRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string - - /** - * When true, create parent directories as needed (mkdir -p). When false (the - * default), a missing parent directory fails with ENOENT. - * - * @generated from field: optional bool recursive = 3; - */ - recursive?: boolean - - /** - * Octal permission mode for the new directory. When unset, the server applies - * its umask. - * - * @generated from field: optional uint32 mode = 4; - */ - mode?: number -} - -/** - * Describes the message depot.sandbox.v1.MkdirRequest. - * Use `create(MkdirRequestSchema)` to create a new message. - */ -export const MkdirRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 1, -) - -/** - * @generated from message depot.sandbox.v1.MkdirResponse - */ -export type MkdirResponse = Message<'depot.sandbox.v1.MkdirResponse'> & {} - -/** - * Describes the message depot.sandbox.v1.MkdirResponse. - * Use `create(MkdirResponseSchema)` to create a new message. - */ -export const MkdirResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 2, -) - -/** - * @generated from message depot.sandbox.v1.StatRequest - */ -export type StatRequest = Message<'depot.sandbox.v1.StatRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string - - /** - * When true (the default), follow symlinks like stat. When false, report on - * the link itself like lstat. - * - * @generated from field: optional bool follow_symlinks = 3; - */ - followSymlinks?: boolean -} - -/** - * Describes the message depot.sandbox.v1.StatRequest. - * Use `create(StatRequestSchema)` to create a new message. - */ -export const StatRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_filesystem, 3) - -/** - * @generated from message depot.sandbox.v1.StatResponse - */ -export type StatResponse = Message<'depot.sandbox.v1.StatResponse'> & { - /** - * @generated from field: string path = 1; - */ - path: string - - /** - * @generated from field: int64 size = 2; - */ - size: bigint - - /** - * @generated from field: uint32 mode = 3; - */ - mode: number - - /** - * @generated from field: depot.sandbox.v1.FileType type = 4; - */ - type: FileType - - /** - * @generated from field: string uname = 5; - */ - uname: string - - /** - * @generated from field: string gname = 6; - */ - gname: string - - /** - * @generated from field: int64 mtime_unix_seconds = 7; - */ - mtimeUnixSeconds: bigint -} - -/** - * Describes the message depot.sandbox.v1.StatResponse. - * Use `create(StatResponseSchema)` to create a new message. - */ -export const StatResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 4, -) - -/** - * @generated from message depot.sandbox.v1.ReadDirRequest - */ -export type ReadDirRequest = Message<'depot.sandbox.v1.ReadDirRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string - - /** - * Matches the withFileTypes option of node:fs/promises readdir. The response - * entries always carry name and type; when this is false the SDK exposes them - * as a plain array of names instead. - * - * @generated from field: optional bool with_file_types = 3; - */ - withFileTypes?: boolean -} - -/** - * Describes the message depot.sandbox.v1.ReadDirRequest. - * Use `create(ReadDirRequestSchema)` to create a new message. - */ -export const ReadDirRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 5, -) - -/** - * @generated from message depot.sandbox.v1.ReadDirResponse - */ -export type ReadDirResponse = Message<'depot.sandbox.v1.ReadDirResponse'> & { - /** - * @generated from field: repeated depot.sandbox.v1.DirEntry entries = 1; - */ - entries: DirEntry[] -} - -/** - * Describes the message depot.sandbox.v1.ReadDirResponse. - * Use `create(ReadDirResponseSchema)` to create a new message. - */ -export const ReadDirResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 6, -) - -/** - * @generated from message depot.sandbox.v1.DirEntry - */ -export type DirEntry = Message<'depot.sandbox.v1.DirEntry'> & { - /** - * @generated from field: string name = 1; - */ - name: string - - /** - * @generated from field: depot.sandbox.v1.FileType type = 2; - */ - type: FileType -} - -/** - * Describes the message depot.sandbox.v1.DirEntry. - * Use `create(DirEntrySchema)` to create a new message. - */ -export const DirEntrySchema: GenMessage = /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_filesystem, 7) - -/** - * @generated from message depot.sandbox.v1.RemoveRequest - */ -export type RemoveRequest = Message<'depot.sandbox.v1.RemoveRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string - - /** - * When true, remove directories and their contents recursively (rm -rf). - * - * @generated from field: optional bool recursive = 3; - */ - recursive?: boolean - - /** - * When true, removing a path that does not exist succeeds instead of failing - * with ENOENT, matching the force option of node fs.rm. - * - * @generated from field: optional bool ignore_missing = 4; - */ - ignoreMissing?: boolean -} - -/** - * Describes the message depot.sandbox.v1.RemoveRequest. - * Use `create(RemoveRequestSchema)` to create a new message. - */ -export const RemoveRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 8, -) - -/** - * @generated from message depot.sandbox.v1.RemoveResponse - */ -export type RemoveResponse = Message<'depot.sandbox.v1.RemoveResponse'> & {} - -/** - * Describes the message depot.sandbox.v1.RemoveResponse. - * Use `create(RemoveResponseSchema)` to create a new message. - */ -export const RemoveResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 9, -) - -/** - * @generated from message depot.sandbox.v1.RenameRequest - */ -export type RenameRequest = Message<'depot.sandbox.v1.RenameRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string from_path = 2; - */ - fromPath: string - - /** - * @generated from field: string to_path = 3; - */ - toPath: string -} - -/** - * Describes the message depot.sandbox.v1.RenameRequest. - * Use `create(RenameRequestSchema)` to create a new message. - */ -export const RenameRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 10, -) - -/** - * @generated from message depot.sandbox.v1.RenameResponse - */ -export type RenameResponse = Message<'depot.sandbox.v1.RenameResponse'> & {} - -/** - * Describes the message depot.sandbox.v1.RenameResponse. - * Use `create(RenameResponseSchema)` to create a new message. - */ -export const RenameResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 11, -) - -/** - * @generated from message depot.sandbox.v1.CopyFileRequest - */ -export type CopyFileRequest = Message<'depot.sandbox.v1.CopyFileRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string from_path = 2; - */ - fromPath: string - - /** - * @generated from field: string to_path = 3; - */ - toPath: string - - /** - * When true, preserve the file's mode, owner, and timestamps where the server - * can (cp -p). - * - * @generated from field: optional bool preserve_metadata = 4; - */ - preserveMetadata?: boolean -} - -/** - * Describes the message depot.sandbox.v1.CopyFileRequest. - * Use `create(CopyFileRequestSchema)` to create a new message. - */ -export const CopyFileRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 12, -) - -/** - * @generated from message depot.sandbox.v1.CopyFileResponse - */ -export type CopyFileResponse = Message<'depot.sandbox.v1.CopyFileResponse'> & {} - -/** - * Describes the message depot.sandbox.v1.CopyFileResponse. - * Use `create(CopyFileResponseSchema)` to create a new message. - */ -export const CopyFileResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 13, -) - -/** - * @generated from message depot.sandbox.v1.TruncateRequest - */ -export type TruncateRequest = Message<'depot.sandbox.v1.TruncateRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string - - /** - * @generated from field: int64 size = 3; - */ - size: bigint -} - -/** - * Describes the message depot.sandbox.v1.TruncateRequest. - * Use `create(TruncateRequestSchema)` to create a new message. - */ -export const TruncateRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 14, -) - -/** - * @generated from message depot.sandbox.v1.TruncateResponse - */ -export type TruncateResponse = Message<'depot.sandbox.v1.TruncateResponse'> & {} - -/** - * Describes the message depot.sandbox.v1.TruncateResponse. - * Use `create(TruncateResponseSchema)` to create a new message. - */ -export const TruncateResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 15, -) - -/** - * @generated from message depot.sandbox.v1.ChmodRequest - */ -export type ChmodRequest = Message<'depot.sandbox.v1.ChmodRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string - - /** - * The new permission mode as an octal integer. The SDK accepts either a - * string ("755") or a number (0o755) and converts it. Required: presence is - * tracked (optional) so the server rejects an omitted mode with - * InvalidArgument instead of silently applying 000, the proto3 zero value. - * Matches MkdirRequest.mode and WriteFileRequest.Init.mode, which are also - * presence-tracked. - * - * @generated from field: optional uint32 mode = 3; - */ - mode?: number -} - -/** - * Describes the message depot.sandbox.v1.ChmodRequest. - * Use `create(ChmodRequestSchema)` to create a new message. - */ -export const ChmodRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 16, -) - -/** - * @generated from message depot.sandbox.v1.ChmodResponse - */ -export type ChmodResponse = Message<'depot.sandbox.v1.ChmodResponse'> & {} - -/** - * Describes the message depot.sandbox.v1.ChmodResponse. - * Use `create(ChmodResponseSchema)` to create a new message. - */ -export const ChmodResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 17, -) - -/** - * @generated from message depot.sandbox.v1.ChownRequest - */ -export type ChownRequest = Message<'depot.sandbox.v1.ChownRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string - - /** - * @generated from field: optional uint32 uid = 3; - */ - uid?: number - - /** - * @generated from field: optional uint32 gid = 4; - */ - gid?: number -} - -/** - * Describes the message depot.sandbox.v1.ChownRequest. - * Use `create(ChownRequestSchema)` to create a new message. - */ -export const ChownRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 18, -) - -/** - * @generated from message depot.sandbox.v1.ChownResponse - */ -export type ChownResponse = Message<'depot.sandbox.v1.ChownResponse'> & {} - -/** - * Describes the message depot.sandbox.v1.ChownResponse. - * Use `create(ChownResponseSchema)` to create a new message. - */ -export const ChownResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 19, -) - -/** - * @generated from message depot.sandbox.v1.SymlinkRequest - */ -export type SymlinkRequest = Message<'depot.sandbox.v1.SymlinkRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * Create link_path as a symlink pointing at target (ln -s target link_path). - * - * @generated from field: string target = 2; - */ - target: string - - /** - * @generated from field: string link_path = 3; - */ - linkPath: string -} - -/** - * Describes the message depot.sandbox.v1.SymlinkRequest. - * Use `create(SymlinkRequestSchema)` to create a new message. - */ -export const SymlinkRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 20, -) - -/** - * @generated from message depot.sandbox.v1.SymlinkResponse - */ -export type SymlinkResponse = Message<'depot.sandbox.v1.SymlinkResponse'> & {} - -/** - * Describes the message depot.sandbox.v1.SymlinkResponse. - * Use `create(SymlinkResponseSchema)` to create a new message. - */ -export const SymlinkResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 21, -) - -/** - * @generated from message depot.sandbox.v1.ReadlinkRequest - */ -export type ReadlinkRequest = Message<'depot.sandbox.v1.ReadlinkRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string -} - -/** - * Describes the message depot.sandbox.v1.ReadlinkRequest. - * Use `create(ReadlinkRequestSchema)` to create a new message. - */ -export const ReadlinkRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 22, -) - -/** - * @generated from message depot.sandbox.v1.ReadlinkResponse - */ -export type ReadlinkResponse = Message<'depot.sandbox.v1.ReadlinkResponse'> & { - /** - * @generated from field: string target = 1; - */ - target: string -} - -/** - * Describes the message depot.sandbox.v1.ReadlinkResponse. - * Use `create(ReadlinkResponseSchema)` to create a new message. - */ -export const ReadlinkResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 23, -) - -/** - * @generated from message depot.sandbox.v1.AccessRequest - */ -export type AccessRequest = Message<'depot.sandbox.v1.AccessRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string - - /** - * Bitmask of access checks to perform (F_OK, R_OK, W_OK, X_OK). When unset, - * the server checks for existence (F_OK). - * - * @generated from field: optional uint32 mode = 3; - */ - mode?: number -} - -/** - * Describes the message depot.sandbox.v1.AccessRequest. - * Use `create(AccessRequestSchema)` to create a new message. - */ -export const AccessRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 24, -) - -/** - * @generated from message depot.sandbox.v1.AccessResponse - */ -export type AccessResponse = Message<'depot.sandbox.v1.AccessResponse'> & {} - -/** - * Describes the message depot.sandbox.v1.AccessResponse. - * Use `create(AccessResponseSchema)` to create a new message. - */ -export const AccessResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 25, -) - -/** - * @generated from message depot.sandbox.v1.ReadFileRequest - */ -export type ReadFileRequest = Message<'depot.sandbox.v1.ReadFileRequest'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string -} - -/** - * Describes the message depot.sandbox.v1.ReadFileRequest. - * Use `create(ReadFileRequestSchema)` to create a new message. - */ -export const ReadFileRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 26, -) - -/** - * One chunk of a file streamed by ReadFile. Chunks arrive in order. The final - * chunk may carry eof = true; the stream closing also signals the end of file. - * - * @generated from message depot.sandbox.v1.FileChunk - */ -export type FileChunk = Message<'depot.sandbox.v1.FileChunk'> & { - /** - * @generated from field: bytes data = 1; - */ - data: Uint8Array - - /** - * @generated from field: optional bool eof = 2; - */ - eof?: boolean -} - -/** - * Describes the message depot.sandbox.v1.FileChunk. - * Use `create(FileChunkSchema)` to create a new message. - */ -export const FileChunkSchema: GenMessage = /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_filesystem, 27) - -/** - * A message in the WriteFile request stream. The first message must carry init; - * every message after it carries data bytes. In init, append selects append - * semantics, and mode is applied if the file is created. - * - * @generated from message depot.sandbox.v1.WriteFileRequest - */ -export type WriteFileRequest = Message<'depot.sandbox.v1.WriteFileRequest'> & { - /** - * @generated from oneof depot.sandbox.v1.WriteFileRequest.input - */ - input: - | { - /** - * @generated from field: depot.sandbox.v1.WriteFileRequest.Init init = 1; - */ - value: WriteFileRequest_Init - case: 'init' - } - | { - /** - * @generated from field: bytes data = 2; - */ - value: Uint8Array - case: 'data' - } - | {case: undefined; value?: undefined} -} - -/** - * Describes the message depot.sandbox.v1.WriteFileRequest. - * Use `create(WriteFileRequestSchema)` to create a new message. - */ -export const WriteFileRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 28, -) - -/** - * @generated from message depot.sandbox.v1.WriteFileRequest.Init - */ -export type WriteFileRequest_Init = Message<'depot.sandbox.v1.WriteFileRequest.Init'> & { - /** - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * @generated from field: string path = 2; - */ - path: string - - /** - * @generated from field: optional uint32 mode = 3; - */ - mode?: number - - /** - * @generated from field: optional bool append = 4; - */ - append?: boolean - - /** - * When true, create the parent directories before opening the file. - * - * @generated from field: optional bool create_directories = 5; - */ - createDirectories?: boolean -} - -/** - * Describes the message depot.sandbox.v1.WriteFileRequest.Init. - * Use `create(WriteFileRequest_InitSchema)` to create a new message. - */ -export const WriteFileRequest_InitSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 28, - 0, -) - -/** - * @generated from message depot.sandbox.v1.WriteFileResponse - */ -export type WriteFileResponse = Message<'depot.sandbox.v1.WriteFileResponse'> & { - /** - * @generated from field: int64 bytes_written = 1; - */ - bytesWritten: bigint -} - -/** - * Describes the message depot.sandbox.v1.WriteFileResponse. - * Use `create(WriteFileResponseSchema)` to create a new message. - */ -export const WriteFileResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_filesystem, - 29, -) - -/** - * File system error codes, carried on a FileSystemErrorDetail. The value of - * each code IS its Linux errno number, so the client derives the negated errno - * by negating the value, with no hand-maintained code<->errno table on either - * side: a native (real-errno) backing maps its errno straight onto the wire, - * and the SDK reads code and errno back off the generated enum. The set covers - * the errnos a file-system syscall can realistically return; a code outside it - * (including one a newer peer adds that an older build doesn't know) reads as - * OTHER, so the contract degrades safely instead of mis-mapping. - * - * UNSPECIFIED (0) is the proto3 zero value and is never sent for a real - * failure. OTHER is a synthetic sentinel above the errno range: it has no errno - * (the SDK reports -1) and is the catch-all when the failure can't be - * classified into a specific code. - * - * Aliased Linux errnos are represented by a single canonical name (EAGAIN, not - * EWOULDBLOCK; EOPNOTSUPP, not ENOTSUP) so each value stays unique. - * - * @generated from enum depot.sandbox.v1.FileSystemErrorCode - */ -export enum FileSystemErrorCode { - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_UNSPECIFIED = 0; - */ - FILESYSTEM_ERROR_CODE_UNSPECIFIED = 0, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EPERM = 1; - */ - FILESYSTEM_ERROR_CODE_EPERM = 1, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_ENOENT = 2; - */ - FILESYSTEM_ERROR_CODE_ENOENT = 2, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EINTR = 4; - */ - FILESYSTEM_ERROR_CODE_EINTR = 4, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EIO = 5; - */ - FILESYSTEM_ERROR_CODE_EIO = 5, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EBADF = 9; - */ - FILESYSTEM_ERROR_CODE_EBADF = 9, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_ENOMEM = 12; - */ - FILESYSTEM_ERROR_CODE_ENOMEM = 12, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EACCES = 13; - */ - FILESYSTEM_ERROR_CODE_EACCES = 13, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EBUSY = 16; - */ - FILESYSTEM_ERROR_CODE_EBUSY = 16, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EEXIST = 17; - */ - FILESYSTEM_ERROR_CODE_EEXIST = 17, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EXDEV = 18; - */ - FILESYSTEM_ERROR_CODE_EXDEV = 18, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_ENOTDIR = 20; - */ - FILESYSTEM_ERROR_CODE_ENOTDIR = 20, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EISDIR = 21; - */ - FILESYSTEM_ERROR_CODE_EISDIR = 21, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EINVAL = 22; - */ - FILESYSTEM_ERROR_CODE_EINVAL = 22, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_ENFILE = 23; - */ - FILESYSTEM_ERROR_CODE_ENFILE = 23, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EMFILE = 24; - */ - FILESYSTEM_ERROR_CODE_EMFILE = 24, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_ETXTBSY = 26; - */ - FILESYSTEM_ERROR_CODE_ETXTBSY = 26, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EFBIG = 27; - */ - FILESYSTEM_ERROR_CODE_EFBIG = 27, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_ENOSPC = 28; - */ - FILESYSTEM_ERROR_CODE_ENOSPC = 28, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EROFS = 30; - */ - FILESYSTEM_ERROR_CODE_EROFS = 30, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EMLINK = 31; - */ - FILESYSTEM_ERROR_CODE_EMLINK = 31, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_ENAMETOOLONG = 36; - */ - FILESYSTEM_ERROR_CODE_ENAMETOOLONG = 36, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_ENOTEMPTY = 39; - */ - FILESYSTEM_ERROR_CODE_ENOTEMPTY = 39, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_ELOOP = 40; - */ - FILESYSTEM_ERROR_CODE_ELOOP = 40, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EOPNOTSUPP = 95; - */ - FILESYSTEM_ERROR_CODE_EOPNOTSUPP = 95, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_EDQUOT = 122; - */ - FILESYSTEM_ERROR_CODE_EDQUOT = 122, - - /** - * @generated from enum value: FILESYSTEM_ERROR_CODE_OTHER = 4096; - */ - FILESYSTEM_ERROR_CODE_OTHER = 4096, -} - -/** - * Describes the enum depot.sandbox.v1.FileSystemErrorCode. - */ -export const FileSystemErrorCodeSchema: GenEnum = /*@__PURE__*/ enumDesc( - file_depot_sandbox_v1_filesystem, - 0, -) - -/** - * The kind of file returned by Stat. The SDK uses it to back the isDirectory, - * isFile, and similar checks on a node:fs/promises-style Stats object. - * - * @generated from enum depot.sandbox.v1.FileType - */ -export enum FileType { - /** - * @generated from enum value: FILE_TYPE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: FILE_TYPE_FILE = 1; - */ - FILE = 1, - - /** - * @generated from enum value: FILE_TYPE_DIRECTORY = 2; - */ - DIRECTORY = 2, - - /** - * @generated from enum value: FILE_TYPE_SYMLINK = 3; - */ - SYMLINK = 3, - - /** - * @generated from enum value: FILE_TYPE_BLOCK_DEVICE = 4; - */ - BLOCK_DEVICE = 4, - - /** - * @generated from enum value: FILE_TYPE_CHARACTER_DEVICE = 5; - */ - CHARACTER_DEVICE = 5, - - /** - * @generated from enum value: FILE_TYPE_FIFO = 6; - */ - FIFO = 6, - - /** - * @generated from enum value: FILE_TYPE_SOCKET = 7; - */ - SOCKET = 7, -} - -/** - * Describes the enum depot.sandbox.v1.FileType. - */ -export const FileTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_depot_sandbox_v1_filesystem, 1) diff --git a/packages/sandbox/src/gen/depot/sandbox/v1/refs_pb.ts b/packages/sandbox/src/gen/depot/sandbox/v1/refs_pb.ts deleted file mode 100644 index f5a01d6..0000000 --- a/packages/sandbox/src/gen/depot/sandbox/v1/refs_pb.ts +++ /dev/null @@ -1,46 +0,0 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts,import_extension=js" -// @generated from file depot/sandbox/v1/refs.proto (package depot.sandbox.v1, syntax proto3) -/* eslint-disable */ - -import type {Message} from '@bufbuild/protobuf' -import type {GenFile, GenMessage} from '@bufbuild/protobuf/codegenv2' -import {fileDesc, messageDesc} from '@bufbuild/protobuf/codegenv2' - -/** - * Describes the file depot/sandbox/v1/refs.proto. - */ -export const file_depot_sandbox_v1_refs: GenFile = /*@__PURE__*/ fileDesc( - 'ChtkZXBvdC9zYW5kYm94L3YxL3JlZnMucHJvdG8SEGRlcG90LnNhbmRib3gudjEiJgoKU2FuZGJveFJlZhIMCgJpZBgBIAEoCUgAQgoKCHNlbGVjdG9yYgZwcm90bzM', -) - -/** - * Names a single sandbox. Today the only way to name one is by its id, but - * the selector oneof leaves room to add other forms later without changing - * how existing references are encoded. - * - * @generated from message depot.sandbox.v1.SandboxRef - */ -export type SandboxRef = Message<'depot.sandbox.v1.SandboxRef'> & { - /** - * @generated from oneof depot.sandbox.v1.SandboxRef.selector - */ - selector: - | { - /** - * Server-assigned identifier for the sandbox, as returned by `CreateSandbox`, - * `GetSandbox`, and `ListSandboxes`. This is the same value carried in the - * `sandboxId` field of a Sandbox. - * - * @generated from field: string id = 1; - */ - value: string - case: 'id' - } - | {case: undefined; value?: undefined} -} - -/** - * Describes the message depot.sandbox.v1.SandboxRef. - * Use `create(SandboxRefSchema)` to create a new message. - */ -export const SandboxRefSchema: GenMessage = /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_refs, 0) diff --git a/packages/sandbox/src/gen/depot/sandbox/v1/sandbox_pb.ts b/packages/sandbox/src/gen/depot/sandbox/v1/sandbox_pb.ts deleted file mode 100644 index 17dfb8c..0000000 --- a/packages/sandbox/src/gen/depot/sandbox/v1/sandbox_pb.ts +++ /dev/null @@ -1,893 +0,0 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts,import_extension=js" -// @generated from file depot/sandbox/v1/sandbox.proto (package depot.sandbox.v1, syntax proto3) -/* eslint-disable */ - -import type {Message} from '@bufbuild/protobuf' -import type {GenEnum, GenFile, GenMessage, GenService} from '@bufbuild/protobuf/codegenv2' -import {enumDesc, fileDesc, messageDesc, serviceDesc} from '@bufbuild/protobuf/codegenv2' -import type {Timestamp} from '@bufbuild/protobuf/wkt' -import {file_google_protobuf_timestamp} from '@bufbuild/protobuf/wkt' -import type {RunCommandRequestSchema, SandboxCommandExecutionEventSchema} from './command_pb.js' -import {file_depot_sandbox_v1_command} from './command_pb.js' -import type { - AccessRequestSchema, - AccessResponseSchema, - ChmodRequestSchema, - ChmodResponseSchema, - ChownRequestSchema, - ChownResponseSchema, - CopyFileRequestSchema, - CopyFileResponseSchema, - FileChunkSchema, - MkdirRequestSchema, - MkdirResponseSchema, - ReadDirRequestSchema, - ReadDirResponseSchema, - ReadFileRequestSchema, - ReadlinkRequestSchema, - ReadlinkResponseSchema, - RemoveRequestSchema, - RemoveResponseSchema, - RenameRequestSchema, - RenameResponseSchema, - StatRequestSchema, - StatResponseSchema, - SymlinkRequestSchema, - SymlinkResponseSchema, - TruncateRequestSchema, - TruncateResponseSchema, - WriteFileRequestSchema, - WriteFileResponseSchema, -} from './filesystem_pb.js' -import {file_depot_sandbox_v1_filesystem} from './filesystem_pb.js' -import type {SandboxRef, SandboxRefSchema} from './refs_pb.js' -import {file_depot_sandbox_v1_refs} from './refs_pb.js' - -/** - * Describes the file depot/sandbox/v1/sandbox.proto. - */ -export const file_depot_sandbox_v1_sandbox: GenFile = /*@__PURE__*/ fileDesc( - 'Ch5kZXBvdC9zYW5kYm94L3YxL3NhbmRib3gucHJvdG8SEGRlcG90LnNhbmRib3gudjEicQoJUmVzb3VyY2VzEhIKBXZjcHVzGAEgASgFSACIAQESFgoJbWVtb3J5X21iGAIgASgFSAGIAQESFAoHZGlza19nYhgDIAEoBUgCiAEBQggKBl92Y3B1c0IMCgpfbWVtb3J5X21iQgoKCF9kaXNrX2diIjoKB1J1bnRpbWUSDwoFbmFtZWQYASABKAlIABITCglpbWFnZV9yZWYYAiABKAlIAEIJCgdydW50aW1lItYGCgdTYW5kYm94EhIKCnNhbmRib3hfaWQYASABKAkSFwoPb3JnYW5pemF0aW9uX2lkGAIgASgJEi8KBnN0YXR1cxgDIAEoDjIfLmRlcG90LnNhbmRib3gudjEuU2FuZGJveFN0YXR1cxIuCgpjcmVhdGVkX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIzCgpzdGFydGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgAiAEBEjMKCnN0b3BwZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAGIAQESMwoKZXhwaXJlc19hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIAogBARIhChR0aW1lb3V0X21zX3JlbWFpbmluZxgIIAEoA0gDiAEBEiAKE2FjdGl2ZV9jcHVfdXNhZ2VfbXMYCSABKANIBIgBARI6Cg1uZXR3b3JrX3VzYWdlGAogASgLMh4uZGVwb3Quc2FuZGJveC52MS5OZXR3b3JrVXNhZ2VIBYgBARIuCglyZXNvdXJjZXMYCyABKAsyGy5kZXBvdC5zYW5kYm94LnYxLlJlc291cmNlcxIqCgdydW50aW1lGAwgASgLMhkuZGVwb3Quc2FuZGJveC52MS5SdW50aW1lEhYKCWV4aXRfY29kZRgOIAEoBUgGiAEBEhoKDWVycm9yX21lc3NhZ2UYDyABKAlIB4gBARIvCgNlbnYYECADKAsyIi5kZXBvdC5zYW5kYm94LnYxLlNhbmRib3guRW52RW50cnkSEQoEbmFtZRgRIAEoCUgIiAEBGioKCEVudkVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAFCDQoLX3N0YXJ0ZWRfYXRCDQoLX3N0b3BwZWRfYXRCDQoLX2V4cGlyZXNfYXRCFwoVX3RpbWVvdXRfbXNfcmVtYWluaW5nQhYKFF9hY3RpdmVfY3B1X3VzYWdlX21zQhAKDl9uZXR3b3JrX3VzYWdlQgwKCl9leGl0X2NvZGVCEAoOX2Vycm9yX21lc3NhZ2VCBwoFX25hbWVKBAgNEA4iOwoMTmV0d29ya1VzYWdlEhUKDWluZ3Jlc3NfYnl0ZXMYASABKAMSFAoMZWdyZXNzX2J5dGVzGAIgASgDIr4CChRDcmVhdGVTYW5kYm94UmVxdWVzdBIRCgRuYW1lGAEgASgJSACIAQESMwoJcmVzb3VyY2VzGAIgASgLMhsuZGVwb3Quc2FuZGJveC52MS5SZXNvdXJjZXNIAYgBARIvCgdydW50aW1lGAMgASgLMhkuZGVwb3Quc2FuZGJveC52MS5SdW50aW1lSAKIAQESPAoDZW52GAQgAygLMi8uZGVwb3Quc2FuZGJveC52MS5DcmVhdGVTYW5kYm94UmVxdWVzdC5FbnZFbnRyeRIUCgdzdGFnaW5nGAUgASgISAOIAQEaKgoIRW52RW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUIHCgVfbmFtZUIMCgpfcmVzb3VyY2VzQgoKCF9ydW50aW1lQgoKCF9zdGFnaW5nIkMKFUNyZWF0ZVNhbmRib3hSZXNwb25zZRIqCgdzYW5kYm94GAEgASgLMhkuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94IkAKEkdldFNhbmRib3hSZXNwb25zZRIqCgdzYW5kYm94GAEgASgLMhkuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94IoUDChRMaXN0U2FuZGJveGVzUmVxdWVzdBIWCglwYWdlX3NpemUYASABKAVIAIgBARIXCgpwYWdlX3Rva2VuGAIgASgJSAGIAQESQgoGZmlsdGVyGAMgASgLMi0uZGVwb3Quc2FuZGJveC52MS5MaXN0U2FuZGJveGVzUmVxdWVzdC5GaWx0ZXJIAogBARrPAQoGRmlsdGVyEi8KBnN0YXRlcxgBIAMoDjIfLmRlcG90LnNhbmRib3gudjEuU2FuZGJveFN0YXR1cxI2Cg1jcmVhdGVkX2FmdGVyGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgAiAEBEjcKDmNyZWF0ZWRfYmVmb3JlGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgBiAEBQhAKDl9jcmVhdGVkX2FmdGVyQhEKD19jcmVhdGVkX2JlZm9yZUIMCgpfcGFnZV9zaXplQg0KC19wYWdlX3Rva2VuQgkKB19maWx0ZXIidwoVTGlzdFNhbmRib3hlc1Jlc3BvbnNlEiwKCXNhbmRib3hlcxgBIAMoCzIZLmRlcG90LnNhbmRib3gudjEuU2FuZGJveBIcCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAlIAIgBAUISChBfbmV4dF9wYWdlX3Rva2VuImcKElN0b3BTYW5kYm94UmVxdWVzdBItCgdzYW5kYm94GAEgASgLMhwuZGVwb3Quc2FuZGJveC52MS5TYW5kYm94UmVmEhUKCGJsb2NraW5nGAIgASgISACIAQFCCwoJX2Jsb2NraW5nIkEKE1N0b3BTYW5kYm94UmVzcG9uc2USKgoHc2FuZGJveBgBIAEoCzIZLmRlcG90LnNhbmRib3gudjEuU2FuZGJveCJjChJLaWxsU2FuZGJveFJlcXVlc3QSLQoHc2FuZGJveBgBIAEoCzIcLmRlcG90LnNhbmRib3gudjEuU2FuZGJveFJlZhITCgZzaWduYWwYAiABKAlIAIgBAUIJCgdfc2lnbmFsIkEKE0tpbGxTYW5kYm94UmVzcG9uc2USKgoHc2FuZGJveBgBIAEoCzIZLmRlcG90LnNhbmRib3gudjEuU2FuZGJveCr3AQoNU2FuZGJveFN0YXR1cxIeChpTQU5EQk9YX1NUQVRVU19VTlNQRUNJRklFRBAAEhoKFlNBTkRCT1hfU1RBVFVTX0NSRUFURUQQARIbChdTQU5EQk9YX1NUQVRVU19BU1NJR05FRBACEhsKF1NBTkRCT1hfU1RBVFVTX1NUQVJUSU5HEAMSGgoWU0FOREJPWF9TVEFUVVNfUlVOTklORxAEEhsKF1NBTkRCT1hfU1RBVFVTX0ZJTklTSEVEEAUSHAoYU0FOREJPWF9TVEFUVVNfQ0FOQ0VMTEVEEAYSGQoVU0FOREJPWF9TVEFUVVNfRkFJTEVEEAcyjg0KDlNhbmRib3hTZXJ2aWNlEmAKDUNyZWF0ZVNhbmRib3gSJi5kZXBvdC5zYW5kYm94LnYxLkNyZWF0ZVNhbmRib3hSZXF1ZXN0GicuZGVwb3Quc2FuZGJveC52MS5DcmVhdGVTYW5kYm94UmVzcG9uc2USUAoKR2V0U2FuZGJveBIcLmRlcG90LnNhbmRib3gudjEuU2FuZGJveFJlZhokLmRlcG90LnNhbmRib3gudjEuR2V0U2FuZGJveFJlc3BvbnNlEmAKDUxpc3RTYW5kYm94ZXMSJi5kZXBvdC5zYW5kYm94LnYxLkxpc3RTYW5kYm94ZXNSZXF1ZXN0GicuZGVwb3Quc2FuZGJveC52MS5MaXN0U2FuZGJveGVzUmVzcG9uc2USWgoLU3RvcFNhbmRib3gSJC5kZXBvdC5zYW5kYm94LnYxLlN0b3BTYW5kYm94UmVxdWVzdBolLmRlcG90LnNhbmRib3gudjEuU3RvcFNhbmRib3hSZXNwb25zZRJaCgtLaWxsU2FuZGJveBIkLmRlcG90LnNhbmRib3gudjEuS2lsbFNhbmRib3hSZXF1ZXN0GiUuZGVwb3Quc2FuZGJveC52MS5LaWxsU2FuZGJveFJlc3BvbnNlEmMKClJ1bkNvbW1hbmQSIy5kZXBvdC5zYW5kYm94LnYxLlJ1bkNvbW1hbmRSZXF1ZXN0Gi4uZGVwb3Quc2FuZGJveC52MS5TYW5kYm94Q29tbWFuZEV4ZWN1dGlvbkV2ZW50MAESSAoFTWtkaXISHi5kZXBvdC5zYW5kYm94LnYxLk1rZGlyUmVxdWVzdBofLmRlcG90LnNhbmRib3gudjEuTWtkaXJSZXNwb25zZRJFCgRTdGF0Eh0uZGVwb3Quc2FuZGJveC52MS5TdGF0UmVxdWVzdBoeLmRlcG90LnNhbmRib3gudjEuU3RhdFJlc3BvbnNlEk4KB1JlYWREaXISIC5kZXBvdC5zYW5kYm94LnYxLlJlYWREaXJSZXF1ZXN0GiEuZGVwb3Quc2FuZGJveC52MS5SZWFkRGlyUmVzcG9uc2USSwoGUmVtb3ZlEh8uZGVwb3Quc2FuZGJveC52MS5SZW1vdmVSZXF1ZXN0GiAuZGVwb3Quc2FuZGJveC52MS5SZW1vdmVSZXNwb25zZRJLCgZSZW5hbWUSHy5kZXBvdC5zYW5kYm94LnYxLlJlbmFtZVJlcXVlc3QaIC5kZXBvdC5zYW5kYm94LnYxLlJlbmFtZVJlc3BvbnNlElEKCENvcHlGaWxlEiEuZGVwb3Quc2FuZGJveC52MS5Db3B5RmlsZVJlcXVlc3QaIi5kZXBvdC5zYW5kYm94LnYxLkNvcHlGaWxlUmVzcG9uc2USUQoIVHJ1bmNhdGUSIS5kZXBvdC5zYW5kYm94LnYxLlRydW5jYXRlUmVxdWVzdBoiLmRlcG90LnNhbmRib3gudjEuVHJ1bmNhdGVSZXNwb25zZRJICgVDaG1vZBIeLmRlcG90LnNhbmRib3gudjEuQ2htb2RSZXF1ZXN0Gh8uZGVwb3Quc2FuZGJveC52MS5DaG1vZFJlc3BvbnNlEkgKBUNob3duEh4uZGVwb3Quc2FuZGJveC52MS5DaG93blJlcXVlc3QaHy5kZXBvdC5zYW5kYm94LnYxLkNob3duUmVzcG9uc2USTgoHU3ltbGluaxIgLmRlcG90LnNhbmRib3gudjEuU3ltbGlua1JlcXVlc3QaIS5kZXBvdC5zYW5kYm94LnYxLlN5bWxpbmtSZXNwb25zZRJRCghSZWFkbGluaxIhLmRlcG90LnNhbmRib3gudjEuUmVhZGxpbmtSZXF1ZXN0GiIuZGVwb3Quc2FuZGJveC52MS5SZWFkbGlua1Jlc3BvbnNlEksKBkFjY2VzcxIfLmRlcG90LnNhbmRib3gudjEuQWNjZXNzUmVxdWVzdBogLmRlcG90LnNhbmRib3gudjEuQWNjZXNzUmVzcG9uc2USTAoIUmVhZEZpbGUSIS5kZXBvdC5zYW5kYm94LnYxLlJlYWRGaWxlUmVxdWVzdBobLmRlcG90LnNhbmRib3gudjEuRmlsZUNodW5rMAESVgoJV3JpdGVGaWxlEiIuZGVwb3Quc2FuZGJveC52MS5Xcml0ZUZpbGVSZXF1ZXN0GiMuZGVwb3Quc2FuZGJveC52MS5Xcml0ZUZpbGVSZXNwb25zZSgBYgZwcm90bzM', - [ - file_depot_sandbox_v1_command, - file_depot_sandbox_v1_filesystem, - file_depot_sandbox_v1_refs, - file_google_protobuf_timestamp, - ], -) - -/** - * Compute resources for a sandbox. Every field is optional; leave a field unset - * to take the server's default for that dimension. The defaults are currently - * chosen by the server and not pinned here. - * - * @generated from message depot.sandbox.v1.Resources - */ -export type Resources = Message<'depot.sandbox.v1.Resources'> & { - /** - * Number of virtual CPUs. Leave unset to take the server default. - * - * @generated from field: optional int32 vcpus = 1; - */ - vcpus?: number - - /** - * Memory in megabytes. Leave unset to take the server default. - * - * @generated from field: optional int32 memory_mb = 2; - */ - memoryMb?: number - - /** - * Disk size in gigabytes. Leave unset to take the server default. - * - * @generated from field: optional int32 disk_gb = 3; - */ - diskGb?: number -} - -/** - * Describes the message depot.sandbox.v1.Resources. - * Use `create(ResourcesSchema)` to create a new message. - */ -export const ResourcesSchema: GenMessage = /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_sandbox, 0) - -/** - * Chooses the runtime a sandbox boots into. Set `imageRef` to an OCI image - * reference. `named` is reserved for a future catalog of curated runtimes - * (for example "node24") resolved by the server; the server rejects any - * `named` value with InvalidArgument today, so set `imageRef` instead. - * - * @generated from message depot.sandbox.v1.Runtime - */ -export type Runtime = Message<'depot.sandbox.v1.Runtime'> & { - /** - * @generated from oneof depot.sandbox.v1.Runtime.runtime - */ - runtime: - | { - /** - * A curated runtime name resolved by the server. Reserved for a future - * catalog and currently rejected with InvalidArgument; use `imageRef` - * instead. - * - * @generated from field: string named = 1; - */ - value: string - case: 'named' - } - | { - /** - * An OCI image reference to boot the sandbox from. - * - * @generated from field: string image_ref = 2; - */ - value: string - case: 'imageRef' - } - | {case: undefined; value?: undefined} -} - -/** - * Describes the message depot.sandbox.v1.Runtime. - * Use `create(RuntimeSchema)` to create a new message. - */ -export const RuntimeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_sandbox, 1) - -/** - * A sandbox and its current state, as returned by the lifecycle methods. Field - * number 13 is reserved: it once held a separate termination reason, which was - * dropped because the `status` value already conveys how a sandbox ended. - * - * @generated from message depot.sandbox.v1.Sandbox - */ -export type Sandbox = Message<'depot.sandbox.v1.Sandbox'> & { - /** - * Unique identifier for the sandbox. - * - * @generated from field: string sandbox_id = 1; - */ - sandboxId: string - - /** - * Organization that owns the sandbox. - * - * @generated from field: string organization_id = 2; - */ - organizationId: string - - /** - * Current state of the sandbox in its lifecycle. - * - * @generated from field: depot.sandbox.v1.SandboxStatus status = 3; - */ - status: SandboxStatus - - /** - * Time the sandbox record was created, in RFC 3339 format. Always populated. - * - * @generated from field: google.protobuf.Timestamp created_at = 4; - */ - createdAt?: Timestamp - - /** - * Time the sandbox started running, in RFC 3339 format. Unset until the - * sandbox starts. - * - * @generated from field: optional google.protobuf.Timestamp started_at = 5; - */ - startedAt?: Timestamp - - /** - * Time the sandbox stopped, in RFC 3339 format. Unset until the sandbox - * reaches a terminal state. - * - * @generated from field: optional google.protobuf.Timestamp stopped_at = 6; - */ - stoppedAt?: Timestamp - - /** - * Time the sandbox will expire, in RFC 3339 format. This is the stored, - * authoritative deadline. Unset when the sandbox has no timeout. - * - * @generated from field: optional google.protobuf.Timestamp expires_at = 7; - */ - expiresAt?: Timestamp - - /** - * Milliseconds remaining until the sandbox expires, computed against the - * current time when the sandbox is read. Unset when the sandbox has no - * timeout. - * - * @generated from field: optional int64 timeout_ms_remaining = 8; - */ - timeoutMsRemaining?: bigint - - /** - * Active CPU time consumed by the sandbox, in milliseconds. Populated only - * after the sandbox reaches a terminal state, and only when metering data is - * available. - * - * @generated from field: optional int64 active_cpu_usage_ms = 9; - */ - activeCpuUsageMs?: bigint - - /** - * Network bytes transferred by the sandbox. Populated only after the sandbox - * reaches a terminal state, and only when metering data is available. - * - * @generated from field: optional depot.sandbox.v1.NetworkUsage network_usage = 10; - */ - networkUsage?: NetworkUsage - - /** - * The resources the server resolved for this sandbox, echoed back so callers - * can see what was actually provisioned. - * - * @generated from field: depot.sandbox.v1.Resources resources = 11; - */ - resources?: Resources - - /** - * The runtime the server resolved for this sandbox, echoed back so callers - * can see what was actually provisioned. - * - * @generated from field: depot.sandbox.v1.Runtime runtime = 12; - */ - runtime?: Runtime - - /** - * Process exit code, in the Unix range 0 to 255. Populated only when the - * sandbox reaches a terminal state. Negative values are reserved for a future - * signal-encoded representation. - * - * @generated from field: optional int32 exit_code = 14; - */ - exitCode?: number - - /** - * Human-readable description of why the sandbox failed. Populated only when - * the sandbox reaches a terminal state. - * - * @generated from field: optional string error_message = 15; - */ - errorMessage?: string - - /** - * Sandbox-level environment variables, echoed back from the create request. - * The server merges these into every command it runs in the sandbox; - * surfacing them here lets callers who fetched the sandbox via GetSandbox or - * ListSandboxes see the inherited variables without consulting the original - * create request. - * - * @generated from field: map env = 16; - */ - env: {[key: string]: string} - - /** - * The human-readable label set when the sandbox was created. Empty when the - * sandbox was created without one. Returned by GetSandbox and ListSandboxes - * so callers can show or filter on the label they chose. - * - * @generated from field: optional string name = 17; - */ - name?: string -} - -/** - * Describes the message depot.sandbox.v1.Sandbox. - * Use `create(SandboxSchema)` to create a new message. - */ -export const SandboxSchema: GenMessage = /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_sandbox, 2) - -/** - * Bytes transferred over the sandbox's primary network interface. Ingress and - * egress are reported separately so each direction stays visible even when one - * dominates. - * - * @generated from message depot.sandbox.v1.NetworkUsage - */ -export type NetworkUsage = Message<'depot.sandbox.v1.NetworkUsage'> & { - /** - * Bytes received by the sandbox. - * - * @generated from field: int64 ingress_bytes = 1; - */ - ingressBytes: bigint - - /** - * Bytes sent by the sandbox. - * - * @generated from field: int64 egress_bytes = 2; - */ - egressBytes: bigint -} - -/** - * Describes the message depot.sandbox.v1.NetworkUsage. - * Use `create(NetworkUsageSchema)` to create a new message. - */ -export const NetworkUsageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_depot_sandbox_v1_sandbox, 3) - -/** - * @generated from message depot.sandbox.v1.CreateSandboxRequest - */ -export type CreateSandboxRequest = Message<'depot.sandbox.v1.CreateSandboxRequest'> & { - /** - * Human-readable label for the sandbox, scoped to the organization. Optional. - * - * @generated from field: optional string name = 1; - */ - name?: string - - /** - * Requested compute resources. The server fills in defaults for any unset - * field. - * - * @generated from field: optional depot.sandbox.v1.Resources resources = 2; - */ - resources?: Resources - - /** - * Requested runtime. Set `imageRef`; `named` is reserved and rejected with - * InvalidArgument until the curated runtime catalog ships. - * - * @generated from field: optional depot.sandbox.v1.Runtime runtime = 3; - */ - runtime?: Runtime - - /** - * Environment variables to apply to the sandbox. They are stored on the - * sandbox and merged into every command run in it, with a command's own - * environment taking precedence on a key collision. Values are passed through - * verbatim, with no shell interpretation or special-file handling. - * - * @generated from field: map env = 4; - */ - env: {[key: string]: string} - - /** - * Place the sandbox on a non-production compute pool instead of - * production. This is an unstable option for Depot-supported testing and may be removed without notice. Leave unset for normal placement. - * Default: false. - * - * @generated from field: optional bool staging = 5; - */ - staging?: boolean -} - -/** - * Describes the message depot.sandbox.v1.CreateSandboxRequest. - * Use `create(CreateSandboxRequestSchema)` to create a new message. - */ -export const CreateSandboxRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 4, -) - -/** - * @generated from message depot.sandbox.v1.CreateSandboxResponse - */ -export type CreateSandboxResponse = Message<'depot.sandbox.v1.CreateSandboxResponse'> & { - /** - * The newly created sandbox. - * - * @generated from field: depot.sandbox.v1.Sandbox sandbox = 1; - */ - sandbox?: Sandbox -} - -/** - * Describes the message depot.sandbox.v1.CreateSandboxResponse. - * Use `create(CreateSandboxResponseSchema)` to create a new message. - */ -export const CreateSandboxResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 5, -) - -/** - * @generated from message depot.sandbox.v1.GetSandboxResponse - */ -export type GetSandboxResponse = Message<'depot.sandbox.v1.GetSandboxResponse'> & { - /** - * The requested sandbox. - * - * @generated from field: depot.sandbox.v1.Sandbox sandbox = 1; - */ - sandbox?: Sandbox -} - -/** - * Describes the message depot.sandbox.v1.GetSandboxResponse. - * Use `create(GetSandboxResponseSchema)` to create a new message. - */ -export const GetSandboxResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 6, -) - -/** - * Lists an organization's sandboxes, with optional filtering by status and - * creation time and token-based pagination. - * - * @generated from message depot.sandbox.v1.ListSandboxesRequest - */ -export type ListSandboxesRequest = Message<'depot.sandbox.v1.ListSandboxesRequest'> & { - /** - * Number of sandboxes to return per page. - * - * @generated from field: optional int32 page_size = 1; - */ - pageSize?: number - - /** - * Token to fetch the next page, taken from a previous response's - * `nextPageToken`. Leave empty to fetch the first page. - * - * @generated from field: optional string page_token = 2; - */ - pageToken?: string - - /** - * Criteria for narrowing the results. Leave unset to list all sandboxes. - * - * @generated from field: optional depot.sandbox.v1.ListSandboxesRequest.Filter filter = 3; - */ - filter?: ListSandboxesRequest_Filter -} - -/** - * Describes the message depot.sandbox.v1.ListSandboxesRequest. - * Use `create(ListSandboxesRequestSchema)` to create a new message. - */ -export const ListSandboxesRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 7, -) - -/** - * Criteria for narrowing the list. All set criteria must match. - * - * @generated from message depot.sandbox.v1.ListSandboxesRequest.Filter - */ -export type ListSandboxesRequest_Filter = Message<'depot.sandbox.v1.ListSandboxesRequest.Filter'> & { - /** - * An array of statuses to include. When non-empty, only sandboxes whose - * status is in this set are returned. Leave empty to include all statuses. - * - * @generated from field: repeated depot.sandbox.v1.SandboxStatus states = 1; - */ - states: SandboxStatus[] - - /** - * Include only sandboxes created at or after this time, in RFC 3339 format. - * - * @generated from field: optional google.protobuf.Timestamp created_after = 2; - */ - createdAfter?: Timestamp - - /** - * Include only sandboxes created at or before this time, in RFC 3339 format. - * - * @generated from field: optional google.protobuf.Timestamp created_before = 3; - */ - createdBefore?: Timestamp -} - -/** - * Describes the message depot.sandbox.v1.ListSandboxesRequest.Filter. - * Use `create(ListSandboxesRequest_FilterSchema)` to create a new message. - */ -export const ListSandboxesRequest_FilterSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 7, - 0, -) - -/** - * @generated from message depot.sandbox.v1.ListSandboxesResponse - */ -export type ListSandboxesResponse = Message<'depot.sandbox.v1.ListSandboxesResponse'> & { - /** - * Sandboxes matching the request, most recently created first. - * - * @generated from field: repeated depot.sandbox.v1.Sandbox sandboxes = 1; - */ - sandboxes: Sandbox[] - - /** - * Token to fetch the next page. Empty when there are no more results. - * - * @generated from field: optional string next_page_token = 2; - */ - nextPageToken?: string -} - -/** - * Describes the message depot.sandbox.v1.ListSandboxesResponse. - * Use `create(ListSandboxesResponseSchema)` to create a new message. - */ -export const ListSandboxesResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 8, -) - -/** - * Request to gracefully shut down a sandbox. The server records the request - * immediately and the sandbox proceeds toward FINISHED. By default the call - * returns as soon as the request is recorded; set `blocking` to wait for the - * sandbox to actually reach a terminal state. - * - * @generated from message depot.sandbox.v1.StopSandboxRequest - */ -export type StopSandboxRequest = Message<'depot.sandbox.v1.StopSandboxRequest'> & { - /** - * The sandbox to stop. - * - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * When true, the call waits for the sandbox to reach a terminal state, up to - * a server-side limit, before returning. When false, it returns as soon as - * the stop request is recorded. Default: false. - * - * @generated from field: optional bool blocking = 2; - */ - blocking?: boolean -} - -/** - * Describes the message depot.sandbox.v1.StopSandboxRequest. - * Use `create(StopSandboxRequestSchema)` to create a new message. - */ -export const StopSandboxRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 9, -) - -/** - * @generated from message depot.sandbox.v1.StopSandboxResponse - */ -export type StopSandboxResponse = Message<'depot.sandbox.v1.StopSandboxResponse'> & { - /** - * The sandbox after the stop request was recorded. Its status reflects the - * current state at read time, typically still RUNNING, or FINISHED if it had - * already terminated by the time the response was built. There is no distinct - * "stopping" status. - * - * @generated from field: depot.sandbox.v1.Sandbox sandbox = 1; - */ - sandbox?: Sandbox -} - -/** - * Describes the message depot.sandbox.v1.StopSandboxResponse. - * Use `create(StopSandboxResponseSchema)` to create a new message. - */ -export const StopSandboxResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 10, -) - -/** - * Request to forcibly terminate a sandbox. This is fire-and-forget: the call - * returns once the termination is requested and the sandbox proceeds toward - * CANCELLED. There is no blocking option. - * - * @generated from message depot.sandbox.v1.KillSandboxRequest - */ -export type KillSandboxRequest = Message<'depot.sandbox.v1.KillSandboxRequest'> & { - /** - * The sandbox to kill. - * - * @generated from field: depot.sandbox.v1.SandboxRef sandbox = 1; - */ - sandbox?: SandboxRef - - /** - * Signal name to send, for example "SIGKILL". Reserved for future use; the - * server currently ignores it and always issues a hard cancel. - * - * @generated from field: optional string signal = 2; - */ - signal?: string -} - -/** - * Describes the message depot.sandbox.v1.KillSandboxRequest. - * Use `create(KillSandboxRequestSchema)` to create a new message. - */ -export const KillSandboxRequestSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 11, -) - -/** - * @generated from message depot.sandbox.v1.KillSandboxResponse - */ -export type KillSandboxResponse = Message<'depot.sandbox.v1.KillSandboxResponse'> & { - /** - * The sandbox after the kill request was recorded. - * - * @generated from field: depot.sandbox.v1.Sandbox sandbox = 1; - */ - sandbox?: Sandbox -} - -/** - * Describes the message depot.sandbox.v1.KillSandboxResponse. - * Use `create(KillSandboxResponseSchema)` to create a new message. - */ -export const KillSandboxResponseSchema: GenMessage = /*@__PURE__*/ messageDesc( - file_depot_sandbox_v1_sandbox, - 12, -) - -/** - * Where a sandbox is in its lifecycle. The values track the underlying - * compute state, but this is a separate type: it can grow values the compute - * layer does not track, without coordinating with - * that schema. - * - * @generated from enum depot.sandbox.v1.SandboxStatus - */ -export enum SandboxStatus { - /** - * Default zero value, used when no status has been set. - * - * @generated from enum value: SANDBOX_STATUS_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * The sandbox record exists but no compute has been assigned to it yet. - * - * @generated from enum value: SANDBOX_STATUS_CREATED = 1; - */ - CREATED = 1, - - /** - * Compute has been assigned, but the VM has not started. - * - * @generated from enum value: SANDBOX_STATUS_ASSIGNED = 2; - */ - ASSIGNED = 2, - - /** - * The VM is booting. - * - * @generated from enum value: SANDBOX_STATUS_STARTING = 3; - */ - STARTING = 3, - - /** - * The VM has booted and is ready to accept commands. - * - * @generated from enum value: SANDBOX_STATUS_RUNNING = 4; - */ - RUNNING = 4, - - /** - * The sandbox terminated cleanly, either from a graceful stop or a normal - * exit. - * - * @generated from enum value: SANDBOX_STATUS_FINISHED = 5; - */ - FINISHED = 5, - - /** - * The sandbox was forcibly terminated by a kill. - * - * @generated from enum value: SANDBOX_STATUS_CANCELLED = 6; - */ - CANCELLED = 6, - - /** - * The sandbox ended in error, for example a failed image pull, an - * out-of-memory condition, or a crash. - * - * @generated from enum value: SANDBOX_STATUS_FAILED = 7; - */ - FAILED = 7, -} - -/** - * Describes the enum depot.sandbox.v1.SandboxStatus. - */ -export const SandboxStatusSchema: GenEnum = /*@__PURE__*/ enumDesc(file_depot_sandbox_v1_sandbox, 0) - -/** - * @generated from service depot.sandbox.v1.SandboxService - */ -export const SandboxService: GenService<{ - /** - * @generated from rpc depot.sandbox.v1.SandboxService.CreateSandbox - */ - createSandbox: { - methodKind: 'unary' - input: typeof CreateSandboxRequestSchema - output: typeof CreateSandboxResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.GetSandbox - */ - getSandbox: { - methodKind: 'unary' - input: typeof SandboxRefSchema - output: typeof GetSandboxResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.ListSandboxes - */ - listSandboxes: { - methodKind: 'unary' - input: typeof ListSandboxesRequestSchema - output: typeof ListSandboxesResponseSchema - } - /** - * Stop is a graceful shutdown and lands the sandbox in FINISHED; kill is a - * forced termination and lands it in CANCELLED. Calling either on a sandbox - * that is already in a terminal state returns FailedPrecondition rather than - * repeating the work. - * - * @generated from rpc depot.sandbox.v1.SandboxService.StopSandbox - */ - stopSandbox: { - methodKind: 'unary' - input: typeof StopSandboxRequestSchema - output: typeof StopSandboxResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.KillSandbox - */ - killSandbox: { - methodKind: 'unary' - input: typeof KillSandboxRequestSchema - output: typeof KillSandboxResponseSchema - } - /** - * server-streaming command execution. The response stream begins - * with a Started event and ends with Finished (or terminates with a Connect - * error). Stdout/stderr arrive on dual rails (raw bytes - * alongside the legacy string oneof). Detached mode (`detached=true`) is in - * beta: the server ends the stream right after Started and the command keeps - * running in the sandbox, but its output is not retained and can't be - * retrieved yet. Reattaching to a detached command's output via AttachCommand - * is a future API. - * - * @generated from rpc depot.sandbox.v1.SandboxService.RunCommand - */ - runCommand: { - methodKind: 'server_streaming' - input: typeof RunCommandRequestSchema - output: typeof SandboxCommandExecutionEventSchema - } - /** - * file system access. These methods give a caller a - * node:fs/promises-shaped view of the sandbox's file system. Their messages - * and enums are defined in filesystem.proto. Failures arrive as Connect - * errors with a FileSystemErrorDetail attached, which the SDK turns back into - * a Node-style error carrying code, syscall, and path. The unary methods - * cover the metadata and mutation operations; ReadFile and WriteFile stream - * file contents so that large files don't have to be buffered whole. - * - * @generated from rpc depot.sandbox.v1.SandboxService.Mkdir - */ - mkdir: { - methodKind: 'unary' - input: typeof MkdirRequestSchema - output: typeof MkdirResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.Stat - */ - stat: { - methodKind: 'unary' - input: typeof StatRequestSchema - output: typeof StatResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.ReadDir - */ - readDir: { - methodKind: 'unary' - input: typeof ReadDirRequestSchema - output: typeof ReadDirResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.Remove - */ - remove: { - methodKind: 'unary' - input: typeof RemoveRequestSchema - output: typeof RemoveResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.Rename - */ - rename: { - methodKind: 'unary' - input: typeof RenameRequestSchema - output: typeof RenameResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.CopyFile - */ - copyFile: { - methodKind: 'unary' - input: typeof CopyFileRequestSchema - output: typeof CopyFileResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.Truncate - */ - truncate: { - methodKind: 'unary' - input: typeof TruncateRequestSchema - output: typeof TruncateResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.Chmod - */ - chmod: { - methodKind: 'unary' - input: typeof ChmodRequestSchema - output: typeof ChmodResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.Chown - */ - chown: { - methodKind: 'unary' - input: typeof ChownRequestSchema - output: typeof ChownResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.Symlink - */ - symlink: { - methodKind: 'unary' - input: typeof SymlinkRequestSchema - output: typeof SymlinkResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.Readlink - */ - readlink: { - methodKind: 'unary' - input: typeof ReadlinkRequestSchema - output: typeof ReadlinkResponseSchema - } - /** - * @generated from rpc depot.sandbox.v1.SandboxService.Access - */ - access: { - methodKind: 'unary' - input: typeof AccessRequestSchema - output: typeof AccessResponseSchema - } - /** - * ReadFile streams the contents of a file as ordered FileChunk messages. - * - * @generated from rpc depot.sandbox.v1.SandboxService.ReadFile - */ - readFile: { - methodKind: 'server_streaming' - input: typeof ReadFileRequestSchema - output: typeof FileChunkSchema - } - /** - * WriteFile consumes a request stream that opens with an Init message naming - * the file, followed by the bytes to write. - * - * @generated from rpc depot.sandbox.v1.SandboxService.WriteFile - */ - writeFile: { - methodKind: 'client_streaming' - input: typeof WriteFileRequestSchema - output: typeof WriteFileResponseSchema - } -}> = /*@__PURE__*/ serviceDesc(file_depot_sandbox_v1_sandbox, 0) diff --git a/packages/sandbox/src/index.ts b/packages/sandbox/src/index.ts deleted file mode 100644 index e1c7a84..0000000 --- a/packages/sandbox/src/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Public surface of @depot/sandbox. -// -// Lifecycle surface: create + get + list + listAll + stop + kill. Sandbox -// instances carry their client, so instance methods and child objects do not -// require callers to pass it again. - -export {DEFAULT_ENDPOINT, createClient, type CreateClientOpts, type SandboxClient} from './client.js' -export { - SandboxCommandExecution, - type SandboxCommandExecutionFinished, - type SandboxCommandExecutionLogChunk, -} from './command.js' -export {FileSystemError, SlowConsumerError, type FileSystemErrorCode} from './errors.js' -export { - DirEntry, - FileSystem, - StatResult, - type CopyFileOpts, - type FileTypeName, - type MkdirOpts, - type ReadFileOpts, - type ReaddirOpts, - type RmOpts, - type WriteFileOpts, -} from './filesystem.js' -export { - Sandbox, - type CreateSandboxOpts, - type KillSandboxOpts, - type ListAllSandboxesOpts, - type ListSandboxesOpts, - type ListSandboxesResult, - type StopSandboxOpts, -} from './sandbox.js' -export type { - ListFilter, - Pagination, - PaginationResult, - Resources, - RunCommandOpts, - Runtime, - SandboxCommandExecutionStatus, - SandboxStatus, -} from './types.js' diff --git a/packages/sandbox/src/k-streaming.test.ts b/packages/sandbox/src/k-streaming.test.ts deleted file mode 100644 index 5f71b54..0000000 --- a/packages/sandbox/src/k-streaming.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import assert from 'node:assert' -import test from 'node:test' -import {SlowConsumerError} from './errors.js' -import {BufferedEventLog} from './k-streaming.js' - -test('append/iterate happy path: producer events arrive in order at the consumer', async () => { - const log = new BufferedEventLog() - const it = log.iterate() - log.append(1) - log.append(2) - log.append(3) - log.end() - const out: number[] = [] - for await (const ev of it) out.push(ev) - assert.deepEqual(out, [1, 2, 3]) -}) - -test('multi-consumer isolation: two iterators each see the full sequence', async () => { - const log = new BufferedEventLog() - const a = log.iterate() - log.append(1) - const b = log.iterate() - log.append(2) - log.append(3) - log.end() - const aOut: number[] = [] - for await (const ev of a) aOut.push(ev) - const bOut: number[] = [] - for await (const ev of b) bOut.push(ev) - assert.deepEqual(aOut, [1, 2, 3]) - assert.deepEqual(bOut, [1, 2, 3]) -}) - -test('end() drains pending events then closes', async () => { - const log = new BufferedEventLog() - const it = log.iterate() - log.append(1) - log.append(2) - log.end() - const out: number[] = [] - for await (const ev of it) out.push(ev) - assert.deepEqual(out, [1, 2]) - assert.equal(log.isClosed, true) -}) - -test('slow consumer overflow surfaces SlowConsumerError; healthy consumers are unaffected', async () => { - const log = new BufferedEventLog({consumerQueueLimit: 2}) - const slow = log.iterate() - const fast = log.iterate() - // Lockstep-drain fast so its queue never exceeds the limit; slow never - // drains so its queue overflows after the third append. - log.append(1) - await fast.next() - log.append(2) - await fast.next() - log.append(3) - await fast.next() - log.append(4) - await fast.next() - - await assert.rejects(async () => { - for await (const _ of slow) { - // pass - } - }, SlowConsumerError) -}) diff --git a/packages/sandbox/src/k-streaming.ts b/packages/sandbox/src/k-streaming.ts deleted file mode 100644 index 0472877..0000000 --- a/packages/sandbox/src/k-streaming.ts +++ /dev/null @@ -1,278 +0,0 @@ -// Client-side multi-consumer event log. -// -// The SDK side does not enforce a byte-cap because the server is authoritative -// for that. The SDK buffer sits between RPC arrival and consumer drain. -// -// Used by `Command` to fan out `command.logs()` consumers from a single -// RunCommand stream. - -import {SlowConsumerError} from './errors.js' - -interface EvictionInfo { - droppedBytes: number -} - -const DEFAULT_MAX_BYTES = 16 * 1024 * 1024 -const DEFAULT_IDLE_EVICT_MS = 30_000 -const DEFAULT_CONSUMER_QUEUE_LIMIT = 1024 - -interface BufferedEventLogOpts { - byteCostOf?: (event: T) => number - maxBytes?: number - idleEvictMs?: number - consumerQueueLimit?: number -} - -interface RingEntry { - event: T - byteCost: number - offset: number -} - -interface Consumer { - queue: T[] - nextOffset: number - resolve: ((value: IteratorResult) => void) | null - reject: ((err: unknown) => void) | null - overflowed: boolean - cancelled: boolean - // Number of queue entries that came from initial replay backfill (not - // live appends). The live-consumer cap measures producer-vs-consumer - // lag, so it has to ignore the replay backlog or a late subscriber to - // a long-running command tips over on the next live append before it's - // had a chance to drain. Decremented as queue entries are pulled by - // next(); once it hits zero we're in pure live-tail mode and the cap - // applies normally. - replayPending: number -} - -export class BufferedEventLog { - private readonly byteCostOf: (event: T) => number - private readonly maxBytes: number - private readonly idleEvictMs: number - private readonly consumerQueueLimit: number - - private readonly ring: RingEntry[] = [] - private head = 0 - private tail = 0 - - private retainedBytes = 0 - private droppedBytes = 0 - private closed = false - private failure: unknown = null - private lastDrainAt: number = Date.now() - - private readonly consumers = new Set>() - - constructor(opts: BufferedEventLogOpts = {}) { - this.byteCostOf = opts.byteCostOf ?? (() => 1) - this.maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES - this.idleEvictMs = opts.idleEvictMs ?? DEFAULT_IDLE_EVICT_MS - this.consumerQueueLimit = opts.consumerQueueLimit ?? DEFAULT_CONSUMER_QUEUE_LIMIT - } - - append(event: T): void { - if (this.closed) { - throw new Error('BufferedEventLog.append() after end()/error()') - } - const byteCost = this.byteCostOf(event) - const entry: RingEntry = {event, byteCost, offset: this.tail} - this.ring.push(entry) - this.tail += 1 - this.retainedBytes += byteCost - this.evictIfNeeded() - this.fanOut(entry) - } - - end(): void { - if (this.closed) return - this.closed = true - for (const consumer of this.consumers) { - if (consumer.resolve && consumer.queue.length === 0) { - const resolve = consumer.resolve - consumer.resolve = null - consumer.reject = null - resolve({value: undefined, done: true}) - } - } - // Drop the buffer's references to all consumers. A still-live iterator - // keeps its own consumer via closure and drains its remaining queue - // (then self-detaches), so this only releases abandoned consumers that - // would otherwise pin their queued events for the buffer's lifetime. - this.consumers.clear() - } - - error(err: unknown): void { - if (this.closed) return - this.closed = true - this.failure = err - for (const consumer of this.consumers) { - if (consumer.reject) { - const reject = consumer.reject - consumer.resolve = null - consumer.reject = null - reject(err) - } - } - // Release references for the same reason as end(); a consumer that never - // drains after error() must not keep its queue/callbacks alive here. - this.consumers.clear() - } - - iterate(opts?: {fromOffset?: number; onEvicted?: (info: EvictionInfo) => void}): AsyncIterableIterator { - const requestedOffset = opts?.fromOffset ?? 0 - const onEvicted = opts?.onEvicted - - if (onEvicted && requestedOffset < this.head && this.droppedBytes > 0) { - onEvicted({droppedBytes: this.droppedBytes}) - } - - const consumer: Consumer = { - queue: [], - nextOffset: Math.max(requestedOffset, this.head), - resolve: null, - reject: null, - overflowed: false, - cancelled: false, - replayPending: 0, - } - - // Initial backfill bypasses the live-consumer queue limit. The limit - // exists to detect a *live* subscriber that can't keep up with the rail, - // not to bound the size of history a late subscriber inherits β€” both at - // attach time (a completed command's full ring being prefilled) and on - // the next live append (the cap would trip before the caller had a - // chance to drain). Track how many entries came from this backfill so - // enqueueForConsumer can ignore them when measuring producer lag. - for (const entry of this.ring) { - if (entry.offset >= consumer.nextOffset) { - consumer.queue.push(entry.event) - consumer.nextOffset = entry.offset + 1 - } - } - consumer.replayPending = consumer.queue.length - - this.consumers.add(consumer) - return this.makeIterator(consumer) - } - - get size(): number { - return this.ring.length - } - get bytesRetained(): number { - return this.retainedBytes - } - get bytesDropped(): number { - return this.droppedBytes - } - get isClosed(): boolean { - return this.closed - } - get hasFailure(): boolean { - return this.failure !== null - } - get consumerCount(): number { - return this.consumers.size - } - - private fanOut(entry: RingEntry): void { - for (const consumer of this.consumers) { - if (consumer.cancelled || consumer.overflowed) continue - if (entry.offset < consumer.nextOffset) continue - this.enqueueForConsumer(consumer, entry.event) - consumer.nextOffset = entry.offset + 1 - } - } - - private enqueueForConsumer(consumer: Consumer, event: T): void { - if (consumer.resolve) { - const resolve = consumer.resolve - consumer.resolve = null - consumer.reject = null - this.lastDrainAt = Date.now() - resolve({value: event, done: false}) - return - } - // Cap only the live tail; the replay backlog gets a free pass until - // the caller drains through it. liveQueued = total queued βˆ’ replay - // backlog still pending; once replayPending hits zero, the cap is the - // ordinary live-only limit. - const liveQueued = consumer.queue.length - consumer.replayPending - if (liveQueued >= this.consumerQueueLimit) { - consumer.overflowed = true - return - } - consumer.queue.push(event) - } - - private evictIfNeeded(): void { - const now = Date.now() - const idleExpired = this.consumers.size > 0 && now - this.lastDrainAt >= this.idleEvictMs - - while (this.ring.length > 0) { - const overByteCap = this.retainedBytes > this.maxBytes - const headOffset = this.ring[0]!.offset - const everyConsumerPast = Array.from(this.consumers).every((c) => c.nextOffset > headOffset) - if (!overByteCap && !(idleExpired && everyConsumerPast)) break - - const evicted = this.ring.shift()! - this.retainedBytes -= evicted.byteCost - this.droppedBytes += evicted.byteCost - this.head = evicted.offset + 1 - } - } - - private makeIterator(consumer: Consumer): AsyncIterableIterator { - const next = async (): Promise> => { - if (consumer.overflowed) { - this.detach(consumer) - throw new SlowConsumerError() - } - if (consumer.cancelled) { - return {value: undefined, done: true} - } - if (consumer.queue.length > 0) { - const event = consumer.queue.shift()! - if (consumer.replayPending > 0) consumer.replayPending -= 1 - this.lastDrainAt = Date.now() - return {value: event, done: false} - } - if (this.failure !== null) { - this.detach(consumer) - throw this.failure - } - if (this.closed) { - this.detach(consumer) - return {value: undefined, done: true} - } - return new Promise>((resolve, reject) => { - consumer.resolve = resolve - consumer.reject = reject - }) - } - const returnFn = async (): Promise> => { - this.detach(consumer) - return {value: undefined, done: true} - } - const throwFn = async (err: unknown): Promise> => { - this.detach(consumer) - throw err - } - const iterator: AsyncIterableIterator = { - [Symbol.asyncIterator]() { - return iterator - }, - next, - return: returnFn, - throw: throwFn, - } - return iterator - } - - private detach(consumer: Consumer): void { - consumer.cancelled = true - consumer.resolve = null - consumer.reject = null - this.consumers.delete(consumer) - } -} diff --git a/packages/sandbox/src/sandbox.test.ts b/packages/sandbox/src/sandbox.test.ts deleted file mode 100644 index f71afb4..0000000 --- a/packages/sandbox/src/sandbox.test.ts +++ /dev/null @@ -1,281 +0,0 @@ -import {create, type MessageInitShape} from '@bufbuild/protobuf' -import {timestampFromDate} from '@bufbuild/protobuf/wkt' -import assert from 'node:assert' -import test from 'node:test' -import type {SandboxClient} from './client.js' -import {FileSystem} from './filesystem.js' -import { - SandboxCommandExecutionEventSchema, - SandboxCommandExecutionEvent_FinishedSchema, - SandboxCommandExecutionEvent_StartedSchema, -} from './gen/depot/sandbox/v1/command_pb.js' -import { - CreateSandboxResponseSchema, - GetSandboxResponseSchema, - ListSandboxesResponseSchema, - SandboxSchema, - SandboxStatus as SandboxStatusProto, - StopSandboxResponseSchema, - type Sandbox as SandboxProto, -} from './gen/depot/sandbox/v1/sandbox_pb.js' -import {Sandbox} from './sandbox.js' - -const CREATED_AT = new Date('2026-06-08T12:00:00.000Z') -const STARTED_AT = new Date('2026-06-08T12:00:01.000Z') -const FINISHED_AT = new Date('2026-06-08T12:00:02.000Z') - -type FakeClient = { - client: SandboxClient - calls: (name: string) => unknown[] - lastRequest: (name: string) => unknown -} - -function fakeClient(overrides: Partial unknown>>): FakeClient { - const requests = new Map() - const handler = (name: string) => { - const fn = overrides[name] - if (!fn) throw new Error(`unexpected RPC call: ${name}`) - return (req: unknown) => { - const calls = requests.get(name) ?? [] - calls.push(req) - requests.set(name, calls) - return fn(req) - } - } - const rpc = new Proxy( - {}, - { - get(_target, prop: string) { - return handler(prop) - }, - }, - ) - return { - client: {rpc, endpoint: 'http://test'} as unknown as SandboxClient, - calls: (name: string) => requests.get(name) ?? [], - lastRequest: (name: string) => { - const calls = requests.get(name) ?? [] - return calls[calls.length - 1] - }, - } -} - -function makeSandbox(overrides: MessageInitShape = {}): SandboxProto { - return create(SandboxSchema, { - sandboxId: 'sbx_1', - organizationId: 'org_1', - status: SandboxStatusProto.RUNNING, - createdAt: timestampFromDate(CREATED_AT), - env: {BASE: 'sandbox'}, - ...overrides, - }) -} - -function createResponse(sandbox: SandboxProto) { - return create(CreateSandboxResponseSchema, {sandbox}) -} - -function makeStartedEvent(cmdId: string) { - return create(SandboxCommandExecutionEventSchema, { - event: { - case: 'started', - value: create(SandboxCommandExecutionEvent_StartedSchema, {cmdId, startedAt: timestampFromDate(STARTED_AT)}), - }, - }) -} - -function makeFinishedEvent(exitCode: number) { - return create(SandboxCommandExecutionEventSchema, { - event: { - case: 'finished', - value: create(SandboxCommandExecutionEvent_FinishedSchema, { - exitCode, - finishedAt: timestampFromDate(FINISHED_AT), - }), - }, - }) -} - -test('Sandbox.create takes an explicit client and binds it to the returned sandbox', async () => { - const recording = fakeClient({ - createSandbox: () => createResponse(makeSandbox({sandboxId: 'sbx_created'})), - stopSandbox: () => - create(StopSandboxResponseSchema, { - sandbox: makeSandbox({sandboxId: 'sbx_created', status: SandboxStatusProto.FINISHED}), - }), - }) - - const sandbox = await Sandbox.create(recording.client, {name: 'explicit-client'}) - assert.equal(sandbox.sandboxId, 'sbx_created') - assert.deepEqual(recording.lastRequest('createSandbox'), { - name: 'explicit-client', - resources: undefined, - runtime: undefined, - env: undefined, - staging: undefined, - }) - - await sandbox.stop() - assert.deepEqual(recording.lastRequest('stopSandbox'), { - sandbox: {selector: {case: 'id', value: 'sbx_created'}}, - blocking: undefined, - }) -}) - -test('Sandbox.get, list, and listAll bind returned sandboxes to the resolved client', async () => { - const recording = fakeClient({ - getSandbox: () => create(GetSandboxResponseSchema, {sandbox: makeSandbox({sandboxId: 'sbx_get'})}), - listSandboxes: (req) => { - const pageToken = (req as {pageToken?: string}).pageToken - if (!pageToken) { - return create(ListSandboxesResponseSchema, { - sandboxes: [makeSandbox({sandboxId: 'sbx_a'})], - nextPageToken: 'next', - }) - } - return create(ListSandboxesResponseSchema, {sandboxes: [makeSandbox({sandboxId: 'sbx_b'})]}) - }, - stopSandbox: () => - create(StopSandboxResponseSchema, { - sandbox: makeSandbox({sandboxId: 'sbx_b', status: SandboxStatusProto.FINISHED}), - }), - }) - - const fetched = await Sandbox.get(recording.client, 'sbx_get') - assert.equal(fetched.sandboxId, 'sbx_get') - assert.deepEqual(recording.lastRequest('getSandbox'), {selector: {case: 'id', value: 'sbx_get'}}) - - const page = await Sandbox.list(recording.client, {pagination: {pageSize: 1}}) - assert.deepEqual( - page.sandboxes.map((sandbox) => sandbox.sandboxId), - ['sbx_a'], - ) - - const all: Sandbox[] = [] - for await (const sandbox of Sandbox.listAll(recording.client, {pageSize: 1})) { - all.push(sandbox) - } - assert.deepEqual( - all.map((sandbox) => sandbox.sandboxId), - ['sbx_a', 'sbx_b'], - ) - - await all[1]?.stop() - assert.deepEqual(recording.lastRequest('stopSandbox'), { - sandbox: {selector: {case: 'id', value: 'sbx_b'}}, - blocking: undefined, - }) -}) - -test('sandboxes fetched from different clients keep using their originating clients', async () => { - const clientA = fakeClient({ - listSandboxes: () => create(ListSandboxesResponseSchema, {sandboxes: [makeSandbox({sandboxId: 'sbx_a'})]}), - stopSandbox: () => - create(StopSandboxResponseSchema, { - sandbox: makeSandbox({sandboxId: 'sbx_a', status: SandboxStatusProto.FINISHED}), - }), - }) - const clientB = fakeClient({ - getSandbox: () => create(GetSandboxResponseSchema, {sandbox: makeSandbox({sandboxId: 'sbx_b'})}), - stopSandbox: () => - create(StopSandboxResponseSchema, { - sandbox: makeSandbox({sandboxId: 'sbx_b', status: SandboxStatusProto.FINISHED}), - }), - }) - - const pageA = await Sandbox.list(clientA.client) - const sandboxB = await Sandbox.get(clientB.client, 'sbx_b') - - await pageA.sandboxes[0]?.stop() - await sandboxB.stop() - - assert.equal(clientA.calls('stopSandbox').length, 1) - assert.equal(clientB.calls('stopSandbox').length, 1) - assert.deepEqual(clientA.lastRequest('stopSandbox'), { - sandbox: {selector: {case: 'id', value: 'sbx_a'}}, - blocking: undefined, - }) - assert.deepEqual(clientB.lastRequest('stopSandbox'), { - sandbox: {selector: {case: 'id', value: 'sbx_b'}}, - blocking: undefined, - }) -}) - -test('Sandbox instance methods use the client captured at creation', async () => { - async function* runCommandStream() { - yield makeStartedEvent('cmd_1') - yield makeFinishedEvent(0) - } - const recording = fakeClient({ - createSandbox: () => createResponse(makeSandbox({sandboxId: 'sbx_bound'})), - stopSandbox: () => - create(StopSandboxResponseSchema, { - sandbox: makeSandbox({sandboxId: 'sbx_bound', status: SandboxStatusProto.FINISHED}), - }), - killSandbox: () => ({sandbox: makeSandbox({sandboxId: 'sbx_bound', status: SandboxStatusProto.CANCELLED})}), - runCommand: () => runCommandStream(), - mkdir: () => ({}), - }) - - const sandbox = await Sandbox.create(recording.client, {env: {BASE: 'sandbox'}}) - - await sandbox.stop({blocking: true}) - assert.deepEqual(recording.lastRequest('stopSandbox'), { - sandbox: {selector: {case: 'id', value: 'sbx_bound'}}, - blocking: true, - }) - - await sandbox.kill({signal: 'SIGKILL'}) - assert.deepEqual(recording.lastRequest('killSandbox'), { - sandbox: {selector: {case: 'id', value: 'sbx_bound'}}, - signal: 'SIGKILL', - }) - - const command = await sandbox.runCommand({cmd: 'echo', args: ['hi'], env: {EXTRA: '1'}}) - const finished = await command.wait() - assert.equal(finished.exitCode, 0) - assert.deepEqual(recording.lastRequest('runCommand'), { - sandbox: {selector: {case: 'id', value: 'sbx_bound'}}, - cmd: 'echo', - args: ['hi'], - cwd: undefined, - env: {EXTRA: '1'}, - sudo: undefined, - detached: undefined, - }) - assert.deepEqual({...command.env}, {BASE: 'sandbox', EXTRA: '1'}) - - const fs = sandbox.fs() - assert.ok(fs instanceof FileSystem) - await fs.mkdir('/work') - assert.deepEqual(recording.lastRequest('mkdir'), { - sandbox: {selector: {case: 'id', value: 'sbx_bound'}}, - path: '/work', - recursive: undefined, - mode: undefined, - }) -}) - -function assertSandboxTypeContract(sandbox: Sandbox, client: SandboxClient): void { - void Sandbox.create(client) - void Sandbox.get(client, 'sbx_1') - void Sandbox.list(client) - void Sandbox.listAll(client) - void sandbox.stop() - void sandbox.kill() - void sandbox.runCommand({cmd: 'echo'}) - void sandbox.fs() - - // @ts-expect-error instance stop uses the bound client, not a passed client - void sandbox.stop(client) - // @ts-expect-error instance kill uses the bound client, not a passed client - void sandbox.kill(client) - // @ts-expect-error instance runCommand uses the bound client, not a passed client - void sandbox.runCommand(client, {cmd: 'echo'}) - // @ts-expect-error instance fs uses the bound client, not a passed client - void sandbox.fs(client) - // @ts-expect-error static create still requires the explicit client boundary - void Sandbox.create() -} - -void assertSandboxTypeContract diff --git a/packages/sandbox/src/sandbox.ts b/packages/sandbox/src/sandbox.ts deleted file mode 100644 index 74fe9d8..0000000 --- a/packages/sandbox/src/sandbox.ts +++ /dev/null @@ -1,608 +0,0 @@ -import {create} from '@bufbuild/protobuf' -import {timestampDate, timestampFromDate, type Timestamp} from '@bufbuild/protobuf/wkt' -import {Code, ConnectError} from '@connectrpc/connect' -import type {SandboxClient} from './client.js' -import {_commandInternals, type SandboxCommandExecution} from './command.js' -import {FileSystem} from './filesystem.js' -import type {SandboxCommandExecutionEvent} from './gen/depot/sandbox/v1/command_pb.js' -import { - ListSandboxesRequest_FilterSchema, - RuntimeSchema, - SandboxStatus as SandboxStatusProto, - type ListSandboxesRequest_Filter as ListSandboxesFilterProto, - type NetworkUsage as NetworkUsageProto, - type Runtime as RuntimeProto, - type Sandbox as SandboxProto, -} from './gen/depot/sandbox/v1/sandbox_pb.js' -import type { - ListFilter, - NetworkUsage, - Pagination, - PaginationResult, - Resources, - RunCommandOpts, - Runtime, - SandboxStatus, -} from './types.js' - -/** - * A sandbox. Each instance represents one sandbox running on the server. - * - * You don't construct this directly; use {@link Sandbox.create}, - * {@link Sandbox.get}, {@link Sandbox.list}, or {@link Sandbox.listAll}. - */ -export class Sandbox { - readonly sandboxId: string - readonly organizationId: string - protected readonly client: SandboxClient - - protected _status: SandboxStatus | undefined - protected _resources: Resources | undefined - protected _runtime: Runtime | undefined - protected _createdAt: Date | undefined - protected _startedAt: Date | undefined - protected _stoppedAt: Date | undefined - protected _expiresAt: Date | undefined - protected _timeoutMsRemaining: number | undefined - protected _activeCpuUsageMs: number | undefined - protected _networkUsage: NetworkUsage | undefined - protected _exitCode: number | undefined - protected _errorMessage: string | undefined - protected _name: string | undefined - // The sandbox's environment variables, as reported by the server. The - // server merges these into every command it runs, so callers can read - // them here without holding onto the original create options. - protected _env: Record = {} - - protected constructor(opts: {client: SandboxClient; sandboxId: string; organizationId: string}) { - this.client = opts.client - this.sandboxId = opts.sandboxId - this.organizationId = opts.organizationId - } - - /** - * The sandbox's current lifecycle status. Undefined when the server hasn't - * yet assigned one. - */ - get status(): SandboxStatus | undefined { - return this._status - } - - /** The resources the server actually allocated to this sandbox. */ - get resources(): Resources | undefined { - return this._resources - } - - /** The runtime the server actually resolved for this sandbox. */ - get runtime(): Runtime | undefined { - return this._runtime - } - - /** When the sandbox was created. Always set. */ - get createdAt(): Date | undefined { - return this._createdAt - } - - /** When the sandbox finished booting and started running. */ - get startedAt(): Date | undefined { - return this._startedAt - } - - /** When the sandbox stopped, if it has reached a terminal status. */ - get stoppedAt(): Date | undefined { - return this._stoppedAt - } - - /** When the sandbox will expire, if it has a timeout. */ - get expiresAt(): Date | undefined { - return this._expiresAt - } - - /** - * Milliseconds left before the sandbox times out, measured when the server - * built this response. Undefined when the sandbox has no timeout. - */ - get timeoutMsRemaining(): number | undefined { - return this._timeoutMsRemaining - } - - /** - * Total CPU time the sandbox used, in milliseconds. Only populated once the - * sandbox has stopped, and only when the server reports metering. It stays - * undefined until server-side metering is in place. - */ - get activeCpuUsageMs(): number | undefined { - return this._activeCpuUsageMs - } - - /** - * Bytes the sandbox sent and received over the network. Populated under the - * same conditions as {@link activeCpuUsageMs}. - */ - get networkUsage(): NetworkUsage | undefined { - return this._networkUsage - } - - /** Exit code of the sandbox's main process. */ - get exitCode(): number | undefined { - return this._exitCode - } - - /** A description of the failure, when the sandbox ended with an error. */ - get errorMessage(): string | undefined { - return this._errorMessage - } - - /** The human-readable label set when the sandbox was created, if any. */ - get name(): string | undefined { - return this._name - } - - /** Create a new sandbox. */ - static async create(client: SandboxClient, opts: CreateSandboxOpts = {}): Promise { - const response = await client.rpc.createSandbox({ - name: opts.name, - resources: opts.resources, - runtime: opts.runtime !== undefined ? runtimeToProto(opts.runtime) : undefined, - env: opts.env, - staging: opts.staging, - }) - const sandbox = response.sandbox - if (!sandbox) { - throw new Error('CreateSandbox response missing `sandbox`') - } - return Sandbox.fromProto(sandbox, client) - } - - /** Fetch a sandbox by its id. */ - static async get(client: SandboxClient, sandboxId: string): Promise { - const response = await client.rpc.getSandbox({selector: {case: 'id', value: sandboxId}}) - const sandbox = response.sandbox - if (!sandbox) { - throw new Error('GetSandbox response missing `sandbox`') - } - return Sandbox.fromProto(sandbox, client) - } - - /** - * Fetch a single page of sandboxes. To page through all of them - * automatically, use {@link Sandbox.listAll}. - */ - static async list(client: SandboxClient, opts: ListSandboxesOpts = {}): Promise { - const response = await client.rpc.listSandboxes({ - pageSize: opts.pagination?.pageSize, - pageToken: opts.pagination?.pageToken, - filter: opts.filter !== undefined ? filterToProto(opts.filter) : undefined, - }) - return { - sandboxes: response.sandboxes.map((s) => Sandbox.fromProto(s, client)), - pagination: {nextPageToken: response.nextPageToken || undefined}, - } - } - - /** @internal Build a Sandbox from the proto returned by the server. */ - protected static fromProto(sandbox: SandboxProto, client: SandboxClient): Sandbox { - const instance = new Sandbox({ - client, - sandboxId: sandbox.sandboxId, - organizationId: sandbox.organizationId, - }) - instance.applyProto(sandbox) - return instance - } - - /** - * Iterate over every sandbox, fetching pages as needed. Yields one sandbox - * at a time and keeps requesting pages until the server has none left. - */ - static async *listAll(client: SandboxClient, opts: ListAllSandboxesOpts = {}): AsyncIterable { - let pageToken: string | undefined = undefined - while (true) { - const page: ListSandboxesResult = await Sandbox.list(client, { - filter: opts.filter, - pagination: {pageSize: opts.pageSize, pageToken}, - }) - for (const sandbox of page.sandboxes) yield sandbox - pageToken = page.pagination.nextPageToken - if (!pageToken) return - } - } - - /** - * Stop the sandbox gracefully. The sandbox ends in the `finished` status. - * - * With `opts.blocking` set, the server waits for the sandbox to finish - * cleaning up before it responds. By default it returns as soon as it has - * recorded the stop request, and you can poll {@link Sandbox.get} to watch - * for the status to change. - * - * This updates the instance in place from the server's response, and throws - * if the sandbox has already stopped. - */ - async stop(opts: StopSandboxOpts = {}): Promise { - const response = await this.client.rpc.stopSandbox({ - sandbox: {selector: {case: 'id', value: this.sandboxId}}, - blocking: opts.blocking, - }) - if (response.sandbox) this.applyProto(response.sandbox) - } - - /** - * Kill the sandbox immediately. The sandbox ends in the `cancelled` status. - * - * `opts.signal` is accepted but currently ignored: every kill is a hard - * cancel regardless of the signal you pass. Support for forwarding the - * signal to the sandbox is planned for a later release. - * - * Throws if the sandbox has already stopped. - */ - async kill(opts: KillSandboxOpts = {}): Promise { - const response = await this.client.rpc.killSandbox({ - sandbox: {selector: {case: 'id', value: this.sandboxId}}, - signal: opts.signal, - }) - if (response.sandbox) this.applyProto(response.sandbox) - } - - /** - * Run a command in this sandbox. This is a server-streaming call, and it - * returns the {@link SandboxCommandExecution} as soon as the server reports - * that the command has started, rather than waiting for it to finish. That - * lets you attach `.logs()`/`.output()` iterators and watch output while the - * command is still running. Call `.wait()` on the returned execution to get - * the settled {@link SandboxCommandExecutionFinished} view: - * - * ```ts - * const cmd = await sandbox.runCommand({cmd: 'echo hi'}) - * const result = await cmd.wait() - * console.log(result.exitCode, await cmd.output()) - * ``` - * - * Returning early is what makes concurrent log streaming possible. The - * stream is buffered with a bounded amount of memory, so if we waited for - * the command to finish before handing control back, the start of the - * output could be discarded before any consumer had a chance to read it. - * Internally we open the stream, build the execution from the start event, - * then drain the rest of the stream into that buffer in the background; - * concurrent `.logs()` callers each get an independent iterator backed by - * the shared buffer. - * - * Detached mode is in beta. When `opts.detached` is set, the command runs - * fire-and-forget: this call returns as soon as the server reports it - * started, and the command keeps running in the sandbox afterward. Its output - * is not retained and cannot be retrieved yet (reattach / log replay is a - * future API), so `logs()`, `output()`, and `wait()` on the returned - * execution throw, and the SDK logs a warning when detached is used. - */ - async runCommand(opts: RunCommandOpts): Promise { - if (opts.detached) { - // Detached is beta and one-way: we launch the command and return, but its - // output is never streamed or stored, so there is nothing for - // logs()/output()/wait() to read. Warn rather than reject so callers can - // start using it, but make the limitation impossible to miss. - console.warn( - '[depot/sandbox] detached commands are in beta: output is not retained and cannot be retrieved yet; ' + - 'logs(), output(), and wait() are unavailable on the returned command', - ) - } - const stream = this.client.rpc.runCommand({ - sandbox: {selector: {case: 'id', value: this.sandboxId}}, - cmd: opts.cmd, - args: opts.args ?? [], - cwd: opts.cwd, - env: opts.env ?? {}, - sudo: opts.sudo, - detached: opts.detached, - }) - - // Pull the first event; it MUST be Started so the SDK can build the - // SandboxCommandExecution instance with the server-minted cmd_id. Anything else - // (including a missing oneof case) is a protocol violation. We don't - // use for-await-of here because we hand the iterator off to - // drainStream once Started lands; the cost is that the implicit - // cleanup for-await-of provides doesn't apply, so any early-exit - // path has to call iterator.return() explicitly or the underlying - // HTTP/2 stream stays open until GC. - const iterator = stream[Symbol.asyncIterator]() - let startedEvent: SandboxCommandExecutionEvent - try { - const first = await iterator.next() - if (first.done) { - throw new ConnectError('RunCommand stream closed before Started event', Code.Internal) - } - startedEvent = first.value - if (startedEvent.event.case !== 'started') { - throw new ConnectError( - `RunCommand stream first event was ${startedEvent.event.case ?? 'unknown'}, expected started`, - Code.Internal, - ) - } - } catch (err) { - if (typeof iterator.return === 'function') { - try { - await iterator.return() - } catch { - // ignore β€” best-effort cleanup - } - } - throw err - } - const startedAt = - startedEvent.event.value.startedAt !== undefined ? timestampDate(startedEvent.event.value.startedAt) : new Date() - - // Merge sandbox-level env + request env (request wins on collisions) so - // SandboxCommandExecution.env on the returned instance matches what the server actually - // executes and what a later GetCommand snapshot reads back. The server - // performs the same merge before persisting the SandboxCommandExecution row. - const mergedEnv = {...this._env, ...opts.env} - const command = _commandInternals.fromStartedEvent({ - cmdId: startedEvent.event.value.cmdId, - sandboxId: this.sandboxId, - cmd: opts.cmd, - args: opts.args ?? [], - cwd: opts.cwd, - env: mergedEnv, - sudo: opts.sudo ?? false, - detached: opts.detached ?? false, - startedAt, - }) - - // Replay the Started event into the buffer so `logs()` consumers - // attached before the next event arrives see the same Started β†’ ... β†’ - // Finished sequence a fresh AttachCommand would receive. - _commandInternals.ingest(command, startedEvent) - - if (opts.detached) { - // Fire-and-forget: the server ends the stream right after Started and - // keeps the command running in the sandbox. There is no further output to - // pump, so close the iterator instead of draining it β€” draining would see - // the stream end without a Finished event and wrongly mark the command - // failed. logs(), output(), and wait() throw for detached commands. - if (typeof iterator.return === 'function') { - try { - await iterator.return() - } catch { - // ignore β€” best-effort cleanup - } - } - return command - } - - // Background pump: drain the stream into the buffer. We don't await the - // pump directly β€” callers observe Finished via `command.wait()`, which - // subscribes to the buffer. Errors on the stream propagate via - // events.error(). - // drainStream owns its own try/catch and routes errors to - // _commandInternals.fail(), so the returned promise never rejects; - // a `.catch()` here would be dead code. We still need to consume the - // promise so an uncaught-rejection lint doesn't fire if drainStream - // is ever changed to surface errors β€” void-mark it explicitly. - void drainStream(command, iterator) - - return command - } - - /** - * The sandbox's file system, as a {@link FileSystem} bound to this sandbox. - * Its methods mirror `node:fs/promises`: - * - * ```ts - * const fs = sandbox.fs() - * await fs.mkdir('/work', {recursive: true}) - * await fs.writeFile('/work/hello.txt', 'hi') - * const stat = await fs.stat('/work/hello.txt') - * ``` - * - * Each call returns a fresh, lightweight {@link FileSystem} bound to the same - * sandbox and client. - */ - fs(): FileSystem { - return new FileSystem({client: this.client, sandboxId: this.sandboxId}) - } - - /** @internal Refresh this instance's fields from a proto returned by the server. */ - protected applyProto(sandbox: SandboxProto): void { - this._status = sandboxStatusFromProto(sandbox.status) - this._resources = resourcesFromProto(sandbox) - this._runtime = runtimeFromProto(sandbox) - this._createdAt = timestampToDate(sandbox.createdAt) - this._startedAt = timestampToDate(sandbox.startedAt) - this._stoppedAt = timestampToDate(sandbox.stoppedAt) - this._expiresAt = timestampToDate(sandbox.expiresAt) - this._timeoutMsRemaining = bigintToNumber(sandbox.timeoutMsRemaining) - this._activeCpuUsageMs = bigintToNumber(sandbox.activeCpuUsageMs) - this._networkUsage = networkUsageFromProto(sandbox.networkUsage) - this._exitCode = sandbox.exitCode - this._errorMessage = sandbox.errorMessage - this._name = sandbox.name - this._env = {...sandbox.env} - } - - /** - * The environment variables the server merges into every command run on - * this sandbox. Empty if the sandbox was created without any, or if it was - * fetched from a server that doesn't report them. - */ - get env(): Readonly> { - return this._env - } -} - -/** Options for {@link Sandbox.stop}. */ -export interface StopSandboxOpts { - /** - * Wait for the sandbox to finish cleaning up before resolving. By default - * the call resolves as soon as the server records the stop request, and you - * can poll {@link Sandbox.get} to watch for the status to change. - */ - blocking?: boolean -} - -/** Options for {@link Sandbox.kill}. */ -export interface KillSandboxOpts { - /** - * Signal name, such as `"SIGKILL"`. Accepted but currently ignored: every - * kill is a hard cancel regardless of the signal you pass. - */ - signal?: string -} - -/** Options for {@link Sandbox.create}. */ -export interface CreateSandboxOpts { - /** An optional name for the sandbox, unique within your organization. */ - name?: string - /** The resources to request. The server fills in defaults for anything you leave unset. */ - resources?: Resources - /** The runtime to use. Set exactly one of `named` or `imageRef`. */ - runtime?: Runtime - /** - * Environment variables for the sandbox. The server merges these into every - * command run on the sandbox. When a command sets the same variable, the - * command's value wins. - */ - env?: Record - /** - * Place the sandbox on a non-production compute pool. This is an unstable - * option for Depot-supported testing and may be removed without notice. - * Leave unset for normal placement. - */ - staging?: boolean -} - -/** Options for {@link Sandbox.list}. */ -export interface ListSandboxesOpts { - pagination?: Pagination - filter?: ListFilter -} - -/** Options for {@link Sandbox.listAll}. */ -export interface ListAllSandboxesOpts { - pageSize?: number - filter?: ListFilter -} - -/** Result of {@link Sandbox.list}. */ -export interface ListSandboxesResult { - sandboxes: Sandbox[] - pagination: PaginationResult -} - -function runtimeToProto(runtime: Runtime): RuntimeProto { - if ('named' in runtime) { - return create(RuntimeSchema, {runtime: {case: 'named', value: runtime.named}}) - } - return create(RuntimeSchema, {runtime: {case: 'imageRef', value: runtime.imageRef}}) -} - -function resourcesFromProto(sandbox: SandboxProto): Resources | undefined { - const r = sandbox.resources - if (!r) return undefined - return {vcpus: r.vcpus, memoryMb: r.memoryMb, diskGb: r.diskGb} -} - -function runtimeFromProto(sandbox: SandboxProto): Runtime | undefined { - const r = sandbox.runtime?.runtime - if (!r || r.case === undefined) return undefined - if (r.case === 'named') return {named: r.value} - return {imageRef: r.value} -} - -function timestampToDate(ts: Timestamp | undefined): Date | undefined { - return ts ? timestampDate(ts) : undefined -} - -// int64 proto fields arrive as bigint, but the SDK exposes them as plain -// numbers because that's easier to work with. The timeout and metering values -// we deal with stay well within Number.MAX_SAFE_INTEGER, so the conversion is -// safe in practice. Values above 2^53 would lose precision. -function bigintToNumber(value: bigint | undefined): number | undefined { - return value !== undefined ? Number(value) : undefined -} - -async function drainStream( - command: SandboxCommandExecution, - iterator: AsyncIterator, -): Promise { - let sawFinished = false - try { - while (true) { - const r = await iterator.next() - if (r.done) break - _commandInternals.ingest(command, r.value) - if (r.value.event.case === 'finished') { - sawFinished = true - break - } - } - if (sawFinished) { - _commandInternals.end(command) - } else { - // Server stream ended without a Finished event. The handler's - // no-exit-code path records the row as failed and then returns - // normally, so end()'ing here would leave the SDK object with - // status 'running' and output()/wait() resolving as if the - // command succeeded. Surface as a failure so callers see the - // discrepancy. - _commandInternals.fail( - command, - new Error(`SandboxCommandExecution ${command.cmdId} stream closed without Finished event`), - ) - } - } catch (err) { - _commandInternals.fail(command, err) - } finally { - // We obtained `iterator` by calling stream[Symbol.asyncIterator]() - // and manually drove it with .next(), so a regular for-await-of's - // implicit cleanup doesn't apply. If we broke out of the loop on - // 'finished' (or `_commandInternals.fail` raised), the underlying - // HTTP/2 stream stays open until GC reclaims the iterator. Signal - // explicitly so the transport can release it now. .return() is - // optional on AsyncIterator; guard for that and swallow errors so - // a misbehaving iterator doesn't shadow whatever we already - // recorded above. - if (typeof iterator.return === 'function') { - try { - await iterator.return() - } catch { - // ignore β€” best-effort cleanup - } - } - } -} - -function networkUsageFromProto(usage: NetworkUsageProto | undefined): NetworkUsage | undefined { - if (!usage) return undefined - return {ingressBytes: Number(usage.ingressBytes), egressBytes: Number(usage.egressBytes)} -} - -// The SDK's status strings are just the lowercased proto enum names, so we can -// convert by looking up the enum's name for a given numeric value. -function sandboxStatusFromProto(status: SandboxStatusProto): SandboxStatus | undefined { - if (status === SandboxStatusProto.UNSPECIFIED) return undefined - // A newer server can send a status this SDK build doesn't know about, which - // arrives as a raw number with no matching enum name. Return undefined for - // those rather than throwing, so an older SDK can still read every other - // field on the sandbox. - const name = SandboxStatusProto[status] - return name === undefined ? undefined : (name.toLowerCase() as SandboxStatus) -} - -function sandboxStatusToProto(status: SandboxStatus): SandboxStatusProto { - const value = SandboxStatusProto[status.toUpperCase() as keyof typeof SandboxStatusProto] - // The status union should always line up with the generated enum, so this - // can only happen if the two have drifted apart, or a caller forced an - // invalid value through a cast. Fail with a clear error instead of sending - // garbage to the server. - if (value === undefined) { - throw new Error(`unknown SandboxStatus: ${status}`) - } - return value -} - -function filterToProto(filter: ListFilter): ListSandboxesFilterProto { - return create(ListSandboxesRequest_FilterSchema, { - states: (filter.states ?? []).map(sandboxStatusToProto), - createdAfter: filter.createdAfter !== undefined ? timestampFromDate(filter.createdAfter) : undefined, - createdBefore: filter.createdBefore !== undefined ? timestampFromDate(filter.createdBefore) : undefined, - }) -} diff --git a/packages/sandbox/src/types.ts b/packages/sandbox/src/types.ts deleted file mode 100644 index 6c08086..0000000 --- a/packages/sandbox/src/types.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Public types for the @depot/sandbox SDK. -// -// The generated proto types in ./gen/ stay close to the wire format: numeric -// enums, camelCase derived from snake_case, and proto Timestamp messages. The -// types here are the friendlier shapes we hand to SDK users instead: string -// unions for enums, Date in place of Timestamp, and discriminated unions in -// place of proto oneofs. - -/** - * The lifecycle status of a sandbox, as a string instead of the wire enum. - * - * The underlying proto enum includes an `UNSPECIFIED` zero value that the - * server never actually returns. Rather than expose it here, the conversion - * helper `sandboxStatusFromProto` maps that case to `undefined`. - */ -export type SandboxStatus = 'created' | 'assigned' | 'starting' | 'running' | 'finished' | 'cancelled' | 'failed' - -/** - * Bytes transferred over the sandbox's primary network interface. This is only - * reported once the sandbox reaches a terminal status, and only when a metering - * source is available server-side; it is `undefined` otherwise. - */ -export interface NetworkUsage { - ingressBytes: number - egressBytes: number -} - -/** - * The compute resources to request for a sandbox. Every field is optional; - * leaving one unset lets the server choose a default. - */ -export interface Resources { - vcpus?: number - memoryMb?: number - diskGb?: number -} - -/** - * Selects which runtime a sandbox boots into. Today the only supported option - * is `imageRef`, an arbitrary OCI image reference. - * - * The `named` variant is reserved for a future catalog of curated runtimes - * (for example `"node24"`) that the server would resolve by name. It is not - * usable yet: the server currently rejects any `named` value, so do not set it - * until the catalog ships. - */ -export type Runtime = {readonly named: string} | {readonly imageRef: string} - -/** Narrows the results of {@link Sandbox.list} and {@link Sandbox.listAll}. */ -export interface ListFilter { - /** When set, only return sandboxes whose status is one of these values. */ - states?: readonly SandboxStatus[] - /** Only return sandboxes created after this time. The bound is exclusive. */ - createdAfter?: Date - /** Only return sandboxes created before this time. The bound is exclusive. */ - createdBefore?: Date -} - -/** Controls how a list request is paginated. */ -export interface Pagination { - /** How many results to return per page. The server caps this, and picks a default when it is unset. */ - pageSize?: number - /** An opaque cursor from a previous call's `nextPageToken`, used to fetch the next page. */ - pageToken?: string -} - -/** The pagination details returned alongside a page of results. `nextPageToken` is undefined on the last page. */ -export interface PaginationResult { - nextPageToken?: string -} - -/** - * Friendly SDK alias of the generated `SandboxCommandExecutionStatus` proto enum. - * - * The proto enum carries a sentinel `UNSPECIFIED` zero value that the server - * never returns; that case is mapped to `undefined` at the SDK boundary - * rather than leaking into the union. - */ -export type SandboxCommandExecutionStatus = 'pending' | 'running' | 'finished' | 'failed' | 'killed' - -/** - * Options bag for {@link Sandbox.runCommand}. - * - * `detached` runs the command fire-and-forget (beta). With it set, - * {@link Sandbox.runCommand} returns as soon as the command starts, and the - * command keeps running in the sandbox afterward β€” but its output is not - * retained and can't be retrieved yet, so `logs()`, `output()`, and `wait()` - * on the returned execution throw, and the SDK logs a warning. Reattaching to a - * detached command's output is a future API. Leave `detached` unset - * for the normal blocking shape, where you stream output and await the result. - */ -export interface RunCommandOpts { - cmd: string - args?: string[] - cwd?: string - env?: Record - sudo?: boolean - detached?: boolean -} diff --git a/packages/sandbox/tsconfig.build.json b/packages/sandbox/tsconfig.build.json deleted file mode 100644 index af8cdbb..0000000 --- a/packages/sandbox/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["node_modules", "dist", "src/**/*.test.ts"] -} diff --git a/packages/sandbox/tsconfig.json b/packages/sandbox/tsconfig.json deleted file mode 100644 index ac169ec..0000000 --- a/packages/sandbox/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "rootDir": "./src", - "outDir": "./dist", - "module": "nodenext", - "target": "esnext", - "lib": ["esnext"], - "types": ["node"], - "sourceMap": true, - "declaration": true, - "declarationMap": true, - "noUncheckedIndexedAccess": true, - "strict": true, - "verbatimModuleSyntax": true, - "isolatedModules": true, - "moduleDetection": "force", - "skipLibCheck": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b3c045..1bef4c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,29 +44,7 @@ importers: version: 0.18.1(prettier@3.3.3) tsup: specifier: ^8.2.4 - version: 8.2.4(tsx@4.22.4)(typescript@5.5.4) - typescript: - specifier: ^5.5.4 - version: 5.5.4 - - packages/sandbox: - dependencies: - '@connectrpc/connect': - specifier: ^2.1.0 - version: 2.1.0(@bufbuild/protobuf@2.8.0) - '@connectrpc/connect-node': - specifier: ^2.1.0 - version: 2.1.0(@bufbuild/protobuf@2.8.0)(@connectrpc/connect@2.1.0(@bufbuild/protobuf@2.8.0)) - devDependencies: - '@bufbuild/protobuf': - specifier: ^2.8.0 - version: 2.8.0 - '@types/node': - specifier: ^22.5.4 - version: 22.5.4 - tsx: - specifier: ^4.20.0 - version: 4.22.4 + version: 8.2.4(typescript@5.5.4) typescript: specifier: ^5.5.4 version: 5.5.4 @@ -113,12 +91,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.28.0': - resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/android-arm64@0.23.1': resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} engines: {node: '>=18'} @@ -131,12 +103,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.28.0': - resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm@0.23.1': resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} engines: {node: '>=18'} @@ -149,12 +115,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.28.0': - resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-x64@0.23.1': resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} engines: {node: '>=18'} @@ -167,12 +127,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.28.0': - resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/darwin-arm64@0.23.1': resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} engines: {node: '>=18'} @@ -185,12 +139,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.28.0': - resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-x64@0.23.1': resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} engines: {node: '>=18'} @@ -203,12 +151,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.28.0': - resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/freebsd-arm64@0.23.1': resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} engines: {node: '>=18'} @@ -221,12 +163,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.28.0': - resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-x64@0.23.1': resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} engines: {node: '>=18'} @@ -239,12 +175,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.0': - resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/linux-arm64@0.23.1': resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} engines: {node: '>=18'} @@ -257,12 +187,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.28.0': - resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm@0.23.1': resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} engines: {node: '>=18'} @@ -275,12 +199,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.28.0': - resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-ia32@0.23.1': resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} engines: {node: '>=18'} @@ -293,12 +211,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.28.0': - resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-loong64@0.23.1': resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} engines: {node: '>=18'} @@ -311,12 +223,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.28.0': - resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-mips64el@0.23.1': resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} engines: {node: '>=18'} @@ -329,12 +235,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.28.0': - resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-ppc64@0.23.1': resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} engines: {node: '>=18'} @@ -347,12 +247,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.28.0': - resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-riscv64@0.23.1': resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} engines: {node: '>=18'} @@ -365,12 +259,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.28.0': - resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-s390x@0.23.1': resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} engines: {node: '>=18'} @@ -383,12 +271,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.28.0': - resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-x64@0.23.1': resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} engines: {node: '>=18'} @@ -401,24 +283,12 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.28.0': - resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/netbsd-arm64@0.25.0': resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.28.0': - resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-x64@0.23.1': resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} engines: {node: '>=18'} @@ -431,12 +301,6 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.0': - resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/openbsd-arm64@0.23.1': resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} @@ -449,12 +313,6 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.28.0': - resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-x64@0.23.1': resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} engines: {node: '>=18'} @@ -467,18 +325,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.0': - resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.28.0': - resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/sunos-x64@0.23.1': resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} engines: {node: '>=18'} @@ -491,12 +337,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.28.0': - resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/win32-arm64@0.23.1': resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} engines: {node: '>=18'} @@ -509,12 +349,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.28.0': - resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-ia32@0.23.1': resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} engines: {node: '>=18'} @@ -527,12 +361,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.28.0': - resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-x64@0.23.1': resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} engines: {node: '>=18'} @@ -545,12 +373,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.28.0': - resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -794,11 +616,6 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.28.0: - resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} - engines: {node: '>=18'} - hasBin: true - execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -1126,11 +943,6 @@ packages: typescript: optional: true - tsx@4.22.4: - resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} - engines: {node: '>=18.0.0'} - hasBin: true - typescript@5.4.5: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} @@ -1198,225 +1010,147 @@ snapshots: '@esbuild/aix-ppc64@0.25.0': optional: true - '@esbuild/aix-ppc64@0.28.0': - optional: true - '@esbuild/android-arm64@0.23.1': optional: true '@esbuild/android-arm64@0.25.0': optional: true - '@esbuild/android-arm64@0.28.0': - optional: true - '@esbuild/android-arm@0.23.1': optional: true '@esbuild/android-arm@0.25.0': optional: true - '@esbuild/android-arm@0.28.0': - optional: true - '@esbuild/android-x64@0.23.1': optional: true '@esbuild/android-x64@0.25.0': optional: true - '@esbuild/android-x64@0.28.0': - optional: true - '@esbuild/darwin-arm64@0.23.1': optional: true '@esbuild/darwin-arm64@0.25.0': optional: true - '@esbuild/darwin-arm64@0.28.0': - optional: true - '@esbuild/darwin-x64@0.23.1': optional: true '@esbuild/darwin-x64@0.25.0': optional: true - '@esbuild/darwin-x64@0.28.0': - optional: true - '@esbuild/freebsd-arm64@0.23.1': optional: true '@esbuild/freebsd-arm64@0.25.0': optional: true - '@esbuild/freebsd-arm64@0.28.0': - optional: true - '@esbuild/freebsd-x64@0.23.1': optional: true '@esbuild/freebsd-x64@0.25.0': optional: true - '@esbuild/freebsd-x64@0.28.0': - optional: true - '@esbuild/linux-arm64@0.23.1': optional: true '@esbuild/linux-arm64@0.25.0': optional: true - '@esbuild/linux-arm64@0.28.0': - optional: true - '@esbuild/linux-arm@0.23.1': optional: true '@esbuild/linux-arm@0.25.0': optional: true - '@esbuild/linux-arm@0.28.0': - optional: true - '@esbuild/linux-ia32@0.23.1': optional: true '@esbuild/linux-ia32@0.25.0': optional: true - '@esbuild/linux-ia32@0.28.0': - optional: true - '@esbuild/linux-loong64@0.23.1': optional: true '@esbuild/linux-loong64@0.25.0': optional: true - '@esbuild/linux-loong64@0.28.0': - optional: true - '@esbuild/linux-mips64el@0.23.1': optional: true '@esbuild/linux-mips64el@0.25.0': optional: true - '@esbuild/linux-mips64el@0.28.0': - optional: true - '@esbuild/linux-ppc64@0.23.1': optional: true '@esbuild/linux-ppc64@0.25.0': optional: true - '@esbuild/linux-ppc64@0.28.0': - optional: true - '@esbuild/linux-riscv64@0.23.1': optional: true '@esbuild/linux-riscv64@0.25.0': optional: true - '@esbuild/linux-riscv64@0.28.0': - optional: true - '@esbuild/linux-s390x@0.23.1': optional: true '@esbuild/linux-s390x@0.25.0': optional: true - '@esbuild/linux-s390x@0.28.0': - optional: true - '@esbuild/linux-x64@0.23.1': optional: true '@esbuild/linux-x64@0.25.0': optional: true - '@esbuild/linux-x64@0.28.0': - optional: true - '@esbuild/netbsd-arm64@0.25.0': optional: true - '@esbuild/netbsd-arm64@0.28.0': - optional: true - '@esbuild/netbsd-x64@0.23.1': optional: true '@esbuild/netbsd-x64@0.25.0': optional: true - '@esbuild/netbsd-x64@0.28.0': - optional: true - '@esbuild/openbsd-arm64@0.23.1': optional: true '@esbuild/openbsd-arm64@0.25.0': optional: true - '@esbuild/openbsd-arm64@0.28.0': - optional: true - '@esbuild/openbsd-x64@0.23.1': optional: true '@esbuild/openbsd-x64@0.25.0': optional: true - '@esbuild/openbsd-x64@0.28.0': - optional: true - - '@esbuild/openharmony-arm64@0.28.0': - optional: true - '@esbuild/sunos-x64@0.23.1': optional: true '@esbuild/sunos-x64@0.25.0': optional: true - '@esbuild/sunos-x64@0.28.0': - optional: true - '@esbuild/win32-arm64@0.23.1': optional: true '@esbuild/win32-arm64@0.25.0': optional: true - '@esbuild/win32-arm64@0.28.0': - optional: true - '@esbuild/win32-ia32@0.23.1': optional: true '@esbuild/win32-ia32@0.25.0': optional: true - '@esbuild/win32-ia32@0.28.0': - optional: true - '@esbuild/win32-x64@0.23.1': optional: true '@esbuild/win32-x64@0.25.0': optional: true - '@esbuild/win32-x64@0.28.0': - optional: true - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -1663,35 +1397,6 @@ snapshots: '@esbuild/win32-ia32': 0.25.0 '@esbuild/win32-x64': 0.25.0 - esbuild@0.28.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.0 - '@esbuild/android-arm': 0.28.0 - '@esbuild/android-arm64': 0.28.0 - '@esbuild/android-x64': 0.28.0 - '@esbuild/darwin-arm64': 0.28.0 - '@esbuild/darwin-x64': 0.28.0 - '@esbuild/freebsd-arm64': 0.28.0 - '@esbuild/freebsd-x64': 0.28.0 - '@esbuild/linux-arm': 0.28.0 - '@esbuild/linux-arm64': 0.28.0 - '@esbuild/linux-ia32': 0.28.0 - '@esbuild/linux-loong64': 0.28.0 - '@esbuild/linux-mips64el': 0.28.0 - '@esbuild/linux-ppc64': 0.28.0 - '@esbuild/linux-riscv64': 0.28.0 - '@esbuild/linux-s390x': 0.28.0 - '@esbuild/linux-x64': 0.28.0 - '@esbuild/netbsd-arm64': 0.28.0 - '@esbuild/netbsd-x64': 0.28.0 - '@esbuild/openbsd-arm64': 0.28.0 - '@esbuild/openbsd-x64': 0.28.0 - '@esbuild/openharmony-arm64': 0.28.0 - '@esbuild/sunos-x64': 0.28.0 - '@esbuild/win32-arm64': 0.28.0 - '@esbuild/win32-ia32': 0.28.0 - '@esbuild/win32-x64': 0.28.0 - execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -1846,11 +1551,9 @@ snapshots: pirates@4.0.6: {} - postcss-load-config@6.0.1(tsx@4.22.4): + postcss-load-config@6.0.1: dependencies: lilconfig: 3.1.2 - optionalDependencies: - tsx: 4.22.4 prettier-plugin-organize-imports@4.0.0(prettier@3.3.3)(typescript@5.5.4): dependencies: @@ -1969,7 +1672,7 @@ snapshots: ts-interface-checker@0.1.13: {} - tsup@8.2.4(tsx@4.22.4)(typescript@5.5.4): + tsup@8.2.4(typescript@5.5.4): dependencies: bundle-require: 5.0.0(esbuild@0.23.1) cac: 6.7.14 @@ -1981,7 +1684,7 @@ snapshots: globby: 11.1.0 joycon: 3.1.1 picocolors: 1.1.0 - postcss-load-config: 6.0.1(tsx@4.22.4) + postcss-load-config: 6.0.1 resolve-from: 5.0.0 rollup: 4.24.0 source-map: 0.8.0-beta.0 @@ -1995,12 +1698,6 @@ snapshots: - tsx - yaml - tsx@4.22.4: - dependencies: - esbuild: 0.28.0 - optionalDependencies: - fsevents: 2.3.3 - typescript@5.4.5: {} typescript@5.5.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index d9368ad..0000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - - . - - packages/*