|
|
|
|
|
if (typeof EventSource === 'undefined') { |
|
|
global.EventSource = require('eventsource'); |
|
|
} |
|
|
const { EventIterator } = require('event-iterator'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VercelSandboxSDK { |
|
|
constructor(baseURL = 'http://localhost:3001') { |
|
|
this.baseURL = baseURL; |
|
|
} |
|
|
|
|
|
|
|
|
static async create(options = {}) { |
|
|
const sdk = new VercelSandboxSDK(); |
|
|
const response = await fetch(`${sdk.baseURL}/api/sandboxes`, { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify(options) |
|
|
}); |
|
|
|
|
|
if (!response.ok) throw new Error(`Failed to create sandbox: ${await response.text()}`); |
|
|
|
|
|
const data = await response.json(); |
|
|
return new SandboxInstance(data.id, sdk.baseURL); |
|
|
} |
|
|
|
|
|
|
|
|
static async findById(sandboxId) { |
|
|
const sdk = new VercelSandboxSDK(); |
|
|
const response = await fetch(`${sdk.baseURL}/api/sandboxes/${sandboxId}`); |
|
|
|
|
|
if (!response.ok) { |
|
|
const error = new Error('Sandbox not found'); |
|
|
error.code = 'sandbox_not_found'; |
|
|
throw error; |
|
|
} |
|
|
|
|
|
return new SandboxInstance(sandboxId, sdk.baseURL); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SandboxInstance { |
|
|
constructor(sandboxId, baseURL) { |
|
|
this.id = sandboxId; |
|
|
this.baseURL = baseURL; |
|
|
|
|
|
|
|
|
this.fs = { |
|
|
write: this.writeFile.bind(this) |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
async run(command) { |
|
|
const response = await fetch(`${this.baseURL}/api/sandboxes/${this.id}/cmd`, { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify({ command }) |
|
|
}); |
|
|
|
|
|
if (!response.ok) throw new Error(`Command failed: ${await response.text()}`); |
|
|
|
|
|
const data = await response.json(); |
|
|
return new CommandInstance(this.id, data.id, this.baseURL); |
|
|
} |
|
|
|
|
|
|
|
|
async writeFile(filePath, content) { |
|
|
const response = await fetch(`${this.baseURL}/api/sandboxes/${this.id}/fs/write`, { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify({ path: filePath, content }) |
|
|
}); |
|
|
|
|
|
if (!response.ok) throw new Error(`File write failed: ${await response.text()}`); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
async getURL() { |
|
|
const response = await fetch(`${this.baseURL}/api/sandboxes/${this.id}/url`); |
|
|
if (!response.ok) throw new Error('Could not get sandbox URL'); |
|
|
const data = await response.json(); |
|
|
return data.url; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CommandInstance { |
|
|
constructor(sandboxId, cmdId, baseURL) { |
|
|
this.sandboxId = sandboxId; |
|
|
this.id = cmdId; |
|
|
this.baseURL = baseURL; |
|
|
} |
|
|
|
|
|
|
|
|
async wait() { |
|
|
const response = await fetch( |
|
|
`${this.baseURL}/api/sandboxes/${this.sandboxId}/cmds/${this.id}` |
|
|
); |
|
|
if (!response.ok) return { exitCode: 1, stdout: '', stderr: 'Failed to get command result' }; |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
return { |
|
|
exitCode: data.exitCode, |
|
|
stdout: async () => data.stdout, |
|
|
stderr: async () => data.stderr, |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
logs() { |
|
|
const url = `${this.baseURL}/api/sandboxes/${this.sandboxId}/cmds/${this.id}/logs`; |
|
|
|
|
|
return new EventIterator(({ push, stop, fail }) => { |
|
|
const eventSource = new EventSource(url); |
|
|
|
|
|
eventSource.onmessage = (event) => { |
|
|
try { |
|
|
const message = JSON.parse(event.data); |
|
|
|
|
|
if (message.type === 'stdout' || message.type === 'stderr') { |
|
|
push({ data: message.data, stream: message.type }); |
|
|
} else if (message.type === 'complete') { |
|
|
stop(); |
|
|
} |
|
|
} catch (e) { } |
|
|
}; |
|
|
|
|
|
eventSource.onerror = () => { |
|
|
eventSource.close(); |
|
|
stop(); |
|
|
}; |
|
|
|
|
|
return () => eventSource.close(); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
module.exports = { Sandbox: VercelSandboxSDK }; |
|
|
|