npm.io
0.0.57 • Published 3d ago

@phpsandbox/sdk

Licence
MIT
Version
0.0.57
Deps
6
Size
845 kB
Vulns
0
Weekly
498

PHPSandbox SDK

TypeScript SDK for working with PHPSandbox notebooks: create environments, edit files, run commands, and stream real-time events.

Installation

npm install @phpsandbox/sdk

Node.js >=18 is required. This package is ESM-only; use import rather than require().

Quick Start

import { PHPSandbox } from '@phpsandbox/sdk';

const token = process.env.PHPSANDBOX_TOKEN;
if (!token) throw new Error('Missing PHPSANDBOX_TOKEN');

const client = new PHPSandbox(token);

// create() initializes the notebook by default
const notebook = await client.notebook.create('php');

await notebook.file.write('index.php', '<?php echo "Hello from PHPSandbox";');

const process = await notebook.terminal.spawn('php', ['index.php']);

const reader = process.output.getReader();
let output = '';
try {
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    output += value;
  }
} finally {
  reader.releaseLock();
}

await process.exit;
console.log(output.trim());

// close websocket resources when done
notebook.dispose();

Authentication

Use an API token:

import { PHPSandbox } from '@phpsandbox/sdk';

const client = new PHPSandbox(process.env.PHPSANDBOX_TOKEN!);

Optional constructor args:

new PHPSandbox('token', 'https://api.phpsandbox.io/v1', {
  debug: false,
  startClosed: true,
});

Notebook Lifecycle

const client = new PHPSandbox(process.env.PHPSANDBOX_TOKEN!);

const created = await client.notebook.create('laravel');
const opened = await client.notebook.open('notebook-id');
await opened.ready();

const fetched = await client.notebook.get('notebook-id');
await fetched.ready();

const forked = await created.fork();
await forked.delete();

const persistent = await client.notebook.create('laravel', {
  title: 'Persistent Laravel',
  persistent: true,
});

const persistentFork = await created.fork({
  persistent: true,
});

console.log(persistent.data.policy);
console.log(persistentFork.data.policy);

Notes:

  • create() and fork() initialize automatically.
  • open() and get() return a notebook instance; call await notebook.ready() before using tools.
  • API notebook creates and forks are ephemeral by default. persistent: true requires an entitled account.

Services At A Glance

Each NotebookInstance exposes service clients:

  • notebook.file (Filesystem)
  • notebook.terminal (Terminal)
  • notebook.container (Container)
  • notebook.shell (Shell)
  • notebook.composer (Composer)
  • notebook.git (Git)
  • notebook.lsp (Lsp)
  • notebook.repl (Repl)
  • notebook.laravel (Laravel)
  • notebook.auth (Auth)
  • notebook.log (Log)
  • notebook.services (Services)
  • notebook.mail (NotebookMail)

Notes:

  • terminal.spawn() and shell.exec() are part of the current okra websocket action set. terminal.start currently is not.
  • notebook.repl.eval() is part of the current okra websocket action set. repl.start is currently not.
  • notebook.laravel is exposed by the SDK, but the current okra websocket action set does not expose Laravel maintenance actions.

Common Operations

Files
await notebook.file.write('README.md', '# App');

const raw = await notebook.file.readFile('README.md');
const text = new TextDecoder().decode(raw);

const files = await notebook.file.find('*.php', {
  includes: ['app/**'],
  excludes: ['vendor/**'],
});

const [hasMore, matches] = await notebook.file.search(
  { pattern: 'class\\s+User', isRegExp: true },
  { maxResults: 20, includes: ['app/**'], excludes: ['vendor/**'] }
);
Terminal
const task = await notebook.terminal.spawn('composer', ['--version']);

task.output.getReader().read().then(({ value }) => {
  console.log(value);
});

const exitCode = await task.exit;
console.log(exitCode);
Shell
const result = await notebook.shell.exec('php -v');
result.throw();
console.log(result.output);
Services
import { notebookBuiltinServices } from '@phpsandbox/sdk';

const services = await notebook.services.list();

console.log(notebookBuiltinServices); // ['redis']

await notebook.services.run('redis');
await notebook.services.run('queue', 'php artisan queue:work');

const snapshot = await notebook.services.logs('redis', { tail: 50 });
console.log(snapshot);

const stream = notebook.services.logs('redis', { follow: true, tail: 25 });
const reader = stream.output.getReader();
const chunk = await reader.read();
console.log(chunk.value);
await stream.stop();
Composer
await notebook.composer.install({ noInteraction: true });
await notebook.composer.require({ packages: ['monolog/monolog'] });

const installed = await notebook.composer.packages();
console.log(installed.map((pkg) => pkg.name));
Git
await notebook.git.checkpoint('Jane Doe <jane@example.com>', 'Initial checkpoint');

await notebook.git.sync(
  'https://github.com/acme/my-repo.git',
  'Jane Doe <jane@example.com>',
  'main',
  process.env.GITHUB_TOKEN,
  'pull'
);

const history = await notebook.git.log('main');
console.log(history[0]);

const review = await notebook.git.review();
await notebook.git.stage({ paths: review.files.filter((file) => file.unstaged).map((file) => file.path) });
await notebook.git.unstage({ path: 'README.md' });
await notebook.git.revert({ path: 'README.md' });
await notebook.git.revert({ path: 'staged-file.php', staged: true });

const fork = await notebook.fork();
const preview = await notebook.git.diff({
  notebookId: fork.data.id,
  token: process.env.PHPSANDBOX_TOKEN,
});

if (preview.status === 'ready') {
  const result = await notebook.git.merge({
    notebookId: fork.data.id,
    token: process.env.PHPSANDBOX_TOKEN,
    expectedBaseRef: preview.baseRef,
    author: 'PHPSandbox <hi@phpsandbox.io>',
    message: 'Merge fork into base',
  });

  if (result.status === 'conflict') {
    console.log(result.conflicts);
  }
}

Use notebookId when diffing or merging from another PHPSandbox notebook. Use url for external Git remotes. notebookId, url, and target are mutually exclusive.

Fork diff requires the base and fork to share Git history. If they do not, diff() returns status: 'unrelated'.

Mail
const state = await notebook.mail.status();

if (!state.enabled) {
  await notebook.mail.enable();
}

const mails = await notebook.mail.list();
const mail = await notebook.mail.get(mails.data[0].hash);

await notebook.mail.delete(mail.hash);
await notebook.mail.disable();
Events
const disposeConnect = notebook.onDidConnect(() => {
  console.log('connected');
});

const disposeFs = notebook.file.watch(
  '/app',
  { recursive: true, excludes: ['vendor/**', 'node_modules/**'] },
  (change) => {
    console.log(change.type, change.path);
  }
);

// later
disposeConnect.dispose();
disposeFs.dispose();

Error Handling

import { ApiError, FilesystemError, FilesystemErrorType } from '@phpsandbox/sdk';

try {
  await notebook.file.readFile('/does-not-exist.php');
} catch (error) {
  if (error instanceof FilesystemError && error.name === FilesystemErrorType.FileNotFound) {
    console.error('Missing file');
  } else if (error instanceof ApiError) {
    console.error(error.status, error.body);
  } else {
    throw error;
  }
}

Browser/CDN Usage

See docs/cdn-usage.md for ESM and script-tag examples.

More Docs

  • Getting started walkthrough: docs/getting-started.md
  • API overview: docs/api-summary.md
  • Examples: examples/README.md

Support

License

MIT

Keywords