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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,12 @@ const sdk = new RunloopSDK({
const devbox = await sdk.devbox.create();

// Execute a synchronous command
const result = await devbox.cmd.exec({ command: 'echo "Hello, World!"' });
const result = await devbox.cmd.exec('echo "Hello, World!"');
console.log('Output:', await result.stdout()); // "Hello, World!"
console.log('Exit code:', result.exitCode); // 0

// Start a long-running HTTP server asynchronously
const serverExec = await devbox.cmd.execAsync({
command: 'npx http-server -p 8080',
});
const serverExec = await devbox.cmd.execAsync('npx http-server -p 8080');
console.log(`Started server with execution ID: ${serverExec.executionId}`);

// Check server status
Expand Down Expand Up @@ -104,7 +102,7 @@ const runloop = new Runloop()

const devboxResult = await runloop.devboxes.createAndAwaitRunning()

await runloop.devboxes.executeAndAwaitCompletion(devboxResult.id, {"command": "touch example.txt"})
await runloop.devboxes.executeAndAwaitCompletion(devboxResult.id, { command: "touch example.txt" })

const snapshotResult = await runloop.devbox.snapshotDisk()
await runloop.devboxes.snapshotDisk(devboxResult.id)
Expand All @@ -118,7 +116,7 @@ import { RunloopSDK } from '@runloop/api-client';
const runloop = new RunloopSDK();

const devbox = await runloop.devbox.create();
await devbox.cmd.exec({ command: "touch example.txt" });
await devbox.cmd.exec("touch example.txt");
const snapshot = await devbox.snapshotDisk();
await snapshot.createDevbox();
...
Expand All @@ -144,7 +142,7 @@ The SDK provides comprehensive error handling with typed exceptions:
```typescript
try {
const devbox = await runloop.devbox.create();
const result = await devbox.cmd.exec({ command: 'invalid-command' });
const result = await devbox.cmd.exec('invalid-command');
} catch (error) {
if (error instanceof RunloopSDK.APIError) {
console.log('API Error:', error.status, error.message);
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export interface ClientOptions {
* import { RunloopSDK } from '@runloop/api-client';
* const runloop = new RunloopSDK();
* const devbox = await runloop.devbox.create();
* const result = await devbox.cmd.exec({ command: 'echo "Hello, World!"' });
* const result = await devbox.cmd.exec('echo "Hello, World!"');
* console.log(result.exitCode);
* ```
*/
Expand Down
6 changes: 3 additions & 3 deletions src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type ContentType = ObjectCreateParams['content_type'];
* ```typescript
* const runloop = new RunloopSDK(); // export RUNLOOP_API_KEY will automatically be used.
* const devbox = await runloop.devbox.create({ name: 'my-devbox' });
* const result = await devbox.cmd.exec({ command: 'echo "Hello, World!"' });
* const result = await devbox.cmd.exec('echo "Hello, World!"');
* console.log(result.exitCode);
* ```
*
Expand Down Expand Up @@ -141,7 +141,7 @@ export class RunloopSDK {
* ```typescript
* const runloop = new RunloopSDK();
* const devbox = await runloop.devbox.create({ name: 'my-devbox' });
* const result = await devbox.cmd.exec({ command: 'echo "Hello, World!"' });
* const result = await devbox.cmd.exec('echo "Hello, World!"');
* ```
*/
export class DevboxOps {
Expand All @@ -162,7 +162,7 @@ export class DevboxOps {
* const runloop = new RunloopSDK();
* const devbox = await runloop.devbox.create({ name: 'my-devbox' });
*
* devbox.cmd.exec({ command: 'echo "Hello, World!"' });
* devbox.cmd.exec('echo "Hello, World!"');
* ...
* ```
*
Expand Down
35 changes: 20 additions & 15 deletions src/sdk/devbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,30 +155,32 @@ export class DevboxCmdOps {
* @example
* ```typescript
* // Simple execution
* const result = await devbox.cmd.exec({ command: 'ls -la' });
* const result = await devbox.cmd.exec('ls -la');
* console.log(await result.stdout());
*
* // With streaming callbacks
* const result = await devbox.cmd.exec({
* command: 'npm install',
* const result = await devbox.cmd.exec('npm install', {
* stdout: (line) => process.stdout.write(line),
* stderr: (line) => process.stderr.write(line),
* });
* ```
*
* @param {DevboxExecuteParams & ExecuteStreamingCallbacks} params - Parameters containing the command, optional shell name, and optional callbacks
* @param {string} command - The command to execute
* @param {Omit<DevboxExecuteParams, 'command'> & ExecuteStreamingCallbacks} [params] - Optional parameters including shell name and callbacks
* @param {Core.RequestOptions & { polling?: Partial<PollingOptions<DevboxAsyncExecutionDetailView>> }} [options] - Request options with optional polling configuration
* @returns {Promise<ExecutionResult>} {@link ExecutionResult} with stdout, stderr, and exit status
*/
async exec(
params: DevboxExecuteParams & ExecuteStreamingCallbacks,
command: string,
params?: Omit<DevboxExecuteParams, 'command'> & ExecuteStreamingCallbacks,
options?: Core.RequestOptions & { polling?: Partial<PollingOptions<DevboxAsyncExecutionDetailView>> },
): Promise<ExecutionResult> {
const hasCallbacks = params.stdout || params.stderr || params.output;
const fullParams = { ...params, command };
const hasCallbacks = fullParams.stdout || fullParams.stderr || fullParams.output;

if (hasCallbacks) {
// With callbacks: use async execution workflow to enable streaming
const { stdout, stderr, output, ...executeParams } = params;
const { stdout, stderr, output, ...executeParams } = fullParams;
const execution = await this.client.devboxes.executeAsync(this.devboxId, executeParams, options);

// Start streaming and await both completion and streaming
Expand All @@ -204,7 +206,7 @@ export class DevboxCmdOps {
return new ExecutionResult(this.client, this.devboxId, execution.execution_id, result);
} else {
// Without callbacks: use existing optimized workflow
const result = await this.client.devboxes.executeAndAwaitCompletion(this.devboxId, params, options);
const result = await this.client.devboxes.executeAndAwaitCompletion(this.devboxId, fullParams, options);
return new ExecutionResult(this.client, this.devboxId, result.execution_id, result);
}
}
Expand All @@ -218,8 +220,7 @@ export class DevboxCmdOps {
*
* @example
* ```typescript
* const execution = await devbox.cmd.execAsync({
* command: 'long-running-task.sh',
* const execution = await devbox.cmd.execAsync('long-running-task.sh', {
* stdout: (line) => console.log(`[LOG] ${line}`),
* });
*
Expand All @@ -231,15 +232,18 @@ export class DevboxCmdOps {
* }
* ```
*
* @param {DevboxExecuteAsyncParams & ExecuteStreamingCallbacks} params - Parameters containing the command, optional shell name, and optional callbacks
* @param {string} command - The command to execute
* @param {Omit<DevboxExecuteAsyncParams, 'command'> & ExecuteStreamingCallbacks} [params] - Optional parameters including shell name and callbacks
* @param {Core.RequestOptions} [options] - Request options
* @returns {Promise<Execution>} {@link Execution} object for tracking and controlling the command
*/
async execAsync(
params: DevboxExecuteAsyncParams & ExecuteStreamingCallbacks,
command: string,
params?: Omit<DevboxExecuteAsyncParams, 'command'> & ExecuteStreamingCallbacks,
options?: Core.RequestOptions,
): Promise<Execution> {
const { stdout, stderr, output, ...executeParams } = params;
const fullParams = { ...params, command };
const { stdout, stderr, output, ...executeParams } = fullParams;
const execution = await this.client.devboxes.executeAsync(this.devboxId, executeParams, options);

// Start streaming in background if callbacks provided
Expand Down Expand Up @@ -359,7 +363,7 @@ export class DevboxFileOps {
*
* const runloop = new RunloopSDK();
* const devbox = await runloop.devbox.create({ name: 'my-devbox' });
* devbox.cmd.exec({ command: 'echo "Hello, World!"' });
* devbox.cmd.exec('echo "Hello, World!"');
* ...
* ```
*
Expand Down Expand Up @@ -403,7 +407,7 @@ export class Devbox {
* const runloop = new RunloopSDK();
* const devbox = await runloop.devbox.create({ name: 'my-devbox' });
*
* devbox.cmd.exec({ command: 'echo "Hello, World!"' });
* devbox.cmd.exec('echo "Hello, World!"');
* ...
* ```
*
Expand Down Expand Up @@ -715,6 +719,7 @@ export class Devbox {
* @example
* ```typescript
* await devbox.suspend();
* // Optionally, wait for the devbox to reach the suspended state
* await devbox.awaitSuspended();
* ```
*
Expand Down
2 changes: 1 addition & 1 deletion src/sdk/execution-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import type { DevboxAsyncExecutionDetailView } from '../resources/devboxes/devbo
* const devbox = runloop.devbox.fromId('devbox-123');
*
* // Get result from synchronous execution
* const result = await devbox.cmd.exec({ command: 'ls -la' });
* const result = await devbox.cmd.exec('ls -la');
*
* // Check success
* if (result.success) {
Expand Down
7 changes: 3 additions & 4 deletions src/sdk/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ import { ExecutionResult } from './execution-result';
* const devbox = runloop.devbox.fromId('devbox-123');
*
* // Start async execution with streaming
* const execution = await devbox.cmd.execAsync({
* command: 'npx http-server -p 8080',
* const execution = await devbox.cmd.execAsync('npx http-server -p 8080', {
* stdout: (line) => console.log(`[LOG] ${line}`),
* stderr: (line) => console.error(`[ERROR] ${line}`),
* });
Expand Down Expand Up @@ -86,7 +85,7 @@ export class Execution {
* ```typescript
* const runloop = new RunloopSDK();
* const devbox = runloop.devbox.fromId('devbox-123');
* const execution = await devbox.cmd.execAsync({ command: 'npm install' });
* const execution = await devbox.cmd.execAsync('npm install');
*
* // Other work while command runs...
*
Expand Down Expand Up @@ -133,7 +132,7 @@ export class Execution {
*
* @example
* ```typescript
* const execution = await devbox.cmd.execAsync({ command: 'npx http-server -p 8080' });
* const execution = await devbox.cmd.execAsync('npx http-server -p 8080');
* const state = await execution.getState();
* console.log(`Status: ${state.status}`);
* ```
Expand Down
6 changes: 3 additions & 3 deletions tests/objects/devbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ describe('Devbox (New API)', () => {

mockClient.devboxes.executeAndAwaitCompletion.mockResolvedValue(mockExecution);

const result = await devbox.cmd.exec({ command: 'echo "Hello World"' });
const result = await devbox.cmd.exec('echo "Hello World"');

expect(mockClient.devboxes.executeAndAwaitCompletion).toHaveBeenCalledWith(
'devbox-123',
Expand All @@ -142,7 +142,7 @@ describe('Devbox (New API)', () => {

mockClient.devboxes.executeAsync.mockResolvedValue(mockExecution);

const result = await devbox.cmd.execAsync({ command: 'sleep 10' });
const result = await devbox.cmd.execAsync('sleep 10');

expect(mockClient.devboxes.executeAsync).toHaveBeenCalledWith(
'devbox-123',
Expand Down Expand Up @@ -288,7 +288,7 @@ describe('Devbox (New API)', () => {
const error = new Error('Command failed');
mockClient.devboxes.executeAndAwaitCompletion.mockRejectedValue(error);

await expect(devbox.cmd.exec({ command: 'failing-command' })).rejects.toThrow('Command failed');
await expect(devbox.cmd.exec('failing-command')).rejects.toThrow('Command failed');
});
});
});
38 changes: 13 additions & 25 deletions tests/smoketests/object-oriented/devbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('smoketest: object-oriented devbox', () => {

test('execute synchronous command', async () => {
expect(devbox).toBeDefined();
const result = await devbox.cmd.exec({ command: 'echo "Hello from SDK!"' });
const result = await devbox.cmd.exec('echo "Hello from SDK!"');
expect(result).toBeDefined();
expect(result.exitCode).toBe(0);
const output = await result.stdout();
Expand All @@ -47,7 +47,7 @@ describe('smoketest: object-oriented devbox', () => {

test('execute asynchronous command', async () => {
expect(devbox).toBeDefined();
const execution = await devbox.cmd.execAsync({ command: 'sleep 2 && echo "Async command completed"' });
const execution = await devbox.cmd.execAsync('sleep 2 && echo "Async command completed"');
expect(execution).toBeDefined();
expect(execution.executionId).toBeTruthy();

Expand Down Expand Up @@ -298,8 +298,7 @@ describe('smoketest: object-oriented devbox', () => {
test('exec with stdout callback', async () => {
const stdoutLines: string[] = [];

const result = await devbox.cmd.exec({
command: 'echo "line1" && echo "line2" && echo "line3"',
const result = await devbox.cmd.exec('echo "line1" && echo "line2" && echo "line3"', {
stdout: (line) => {
stdoutLines.push(line);
},
Expand All @@ -319,8 +318,7 @@ describe('smoketest: object-oriented devbox', () => {
test('exec with stderr callback', async () => {
const stderrLines: string[] = [];

const result = await devbox.cmd.exec({
command: 'echo "error1" >&2 && echo "error2" >&2',
const result = await devbox.cmd.exec('echo "error1" >&2 && echo "error2" >&2', {
stderr: (line) => {
stderrLines.push(line);
},
Expand All @@ -339,8 +337,7 @@ describe('smoketest: object-oriented devbox', () => {
test('exec with output callback (both stdout and stderr)', async () => {
const allLines: string[] = [];

const result = await devbox.cmd.exec({
command: 'echo "stdout1" && echo "stderr1" >&2 && echo "stdout2"',
const result = await devbox.cmd.exec('echo "stdout1" && echo "stderr1" >&2 && echo "stdout2"', {
output: (line) => {
allLines.push(line);
},
Expand All @@ -360,8 +357,7 @@ describe('smoketest: object-oriented devbox', () => {
const stderrLines: string[] = [];
const outputLines: string[] = [];

const result = await devbox.cmd.exec({
command: 'echo "out1" && echo "err1" >&2 && echo "out2"',
const result = await devbox.cmd.exec('echo "out1" && echo "err1" >&2 && echo "out2"', {
stdout: (line) => stdoutLines.push(line),
stderr: (line) => stderrLines.push(line),
output: (line) => outputLines.push(line),
Expand All @@ -387,9 +383,7 @@ describe('smoketest: object-oriented devbox', () => {
});

test('exec WITHOUT callbacks (preserve existing behavior)', async () => {
const result = await devbox.cmd.exec({
command: 'echo "test output"',
});
const result = await devbox.cmd.exec('echo "test output"');

expect(result.success).toBe(true);
expect(result.exitCode).toBe(0);
Expand All @@ -402,8 +396,7 @@ describe('smoketest: object-oriented devbox', () => {
let receivedBeforeCompletion = false;

// Start async execution with streaming
const execution = await devbox.cmd.execAsync({
command: 'echo "immediate" && sleep 2 && echo "delayed"',
const execution = await devbox.cmd.execAsync('echo "immediate" && sleep 2 && echo "delayed"', {
stdout: (line) => {
stdoutLines.push(line);
if (line.includes('immediate')) {
Expand Down Expand Up @@ -434,8 +427,7 @@ describe('smoketest: object-oriented devbox', () => {
test('execAsync with stderr callback', async () => {
const stderrLines: string[] = [];

const execution = await devbox.cmd.execAsync({
command: 'echo "error output" >&2',
const execution = await devbox.cmd.execAsync('echo "error output" >&2', {
stderr: (line) => {
stderrLines.push(line);
},
Expand All @@ -455,8 +447,7 @@ describe('smoketest: object-oriented devbox', () => {
const stdoutLines: string[] = [];
const stderrLines: string[] = [];

const result = await devbox.cmd.exec({
command: 'echo "to stdout" && echo "to stderr" >&2 && echo "more stdout"',
const result = await devbox.cmd.exec('echo "to stdout" && echo "to stderr" >&2 && echo "more stdout"', {
stdout: (line) => stdoutLines.push(line),
stderr: (line) => stderrLines.push(line),
});
Expand All @@ -480,8 +471,7 @@ describe('smoketest: object-oriented devbox', () => {
test('exec with long output - verify all lines received', async () => {
const stdoutLines: string[] = [];

const result = await devbox.cmd.exec({
command: 'for i in {1..1000}; do echo "line $i"; done',
const result = await devbox.cmd.exec('for i in {1..1000}; do echo "line $i"; done', {
stdout: (line) => stdoutLines.push(line),
});

Expand Down Expand Up @@ -511,16 +501,14 @@ describe('smoketest: object-oriented devbox', () => {
let taskBCount = 0;

// Start both executions at the same time (don't await)
const executionA = devbox.cmd.execAsync({
command: 'echo "A1" && sleep 0.5 && echo "A2" && sleep 0.5 && echo "A3"',
const executionA = devbox.cmd.execAsync('echo "A1" && sleep 0.5 && echo "A2" && sleep 0.5 && echo "A3"', {
stdout: (line) => {
taskALogs.push(line);
taskACount++;
},
});

const executionB = devbox.cmd.execAsync({
command: 'sleep 0.3 && echo "B1" && sleep 0.5 && echo "B2" && sleep 0.5 && echo "B3"',
const executionB = devbox.cmd.execAsync('sleep 0.3 && echo "B1" && sleep 0.5 && echo "B2" && sleep 0.5 && echo "B3"', {
stdout: (line) => {
taskBLogs.push(line);
taskBCount++;
Expand Down
Loading
Loading