@computesdk/cloudflare 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +288 -0
- package/dist/index.d.mts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +330 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +305 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 computesdk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# @computesdk/cloudflare
|
|
2
|
+
|
|
3
|
+
Cloudflare provider for ComputeSDK - execute code in secure sandboxes on Cloudflare's edge network using Durable Objects.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **๐ Secure Isolation**: Each sandbox runs in its own container with full process isolation
|
|
8
|
+
- **โก Edge-Native**: Runs on Cloudflare's global network for low latency worldwide
|
|
9
|
+
- **๐ Full Filesystem Support**: Read, write, and manage files within the sandbox
|
|
10
|
+
- **๐ง Command Execution**: Run any command or process inside the container
|
|
11
|
+
- **๐ Port Forwarding**: Expose services running in your sandbox via public URLs
|
|
12
|
+
- **๐ Git Integration**: Clone repositories directly into sandboxes
|
|
13
|
+
- **๐งช Code Interpreter**: Execute Python and JavaScript with rich outputs
|
|
14
|
+
- **๐ฎ Session Management**: Maintain state across multiple operations
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @computesdk/cloudflare
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
This provider requires a Cloudflare Workers environment with Durable Objects configured. You'll need:
|
|
25
|
+
|
|
26
|
+
Note: Cloudflare API key should include the following permissions:
|
|
27
|
+
- Workers Scripts:Edit
|
|
28
|
+
- Workers KV Storage:Edit
|
|
29
|
+
- Account Settings:Read
|
|
30
|
+
- Workers Scripts:Read
|
|
31
|
+
- Workers KV Storage:Read
|
|
32
|
+
- Workers Tail:Read
|
|
33
|
+
|
|
34
|
+
1. **Cloudflare Workers account** with Durable Objects enabled
|
|
35
|
+
2. **wrangler.toml configuration** with Sandbox Durable Object binding
|
|
36
|
+
3. **Dockerfile** setup (temporary requirement)
|
|
37
|
+
|
|
38
|
+
### Setup Instructions
|
|
39
|
+
|
|
40
|
+
1. **Create a Dockerfile** (temporary requirement):
|
|
41
|
+
```dockerfile
|
|
42
|
+
FROM docker.io/cloudflare/sandbox:0.3.0
|
|
43
|
+
|
|
44
|
+
# Expose the ports you want to expose
|
|
45
|
+
EXPOSE 3000
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. **Configure wrangler.toml**:
|
|
49
|
+
```toml
|
|
50
|
+
[durable_objects]
|
|
51
|
+
bindings = [
|
|
52
|
+
{ name = "Sandbox", class_name = "Sandbox" }
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[[migrations]]
|
|
56
|
+
tag = "v1"
|
|
57
|
+
new_sqlite_classes = ["Sandbox"]
|
|
58
|
+
|
|
59
|
+
[[containers]]
|
|
60
|
+
class_name = "Sandbox"
|
|
61
|
+
image = "./Dockerfile"
|
|
62
|
+
max_instances = 1
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
3. **Export the Sandbox class in your Worker**:
|
|
66
|
+
```typescript
|
|
67
|
+
import { getSandbox } from "@cloudflare/sandbox";
|
|
68
|
+
|
|
69
|
+
// Export the Sandbox class in your Worker
|
|
70
|
+
export { Sandbox } from "@cloudflare/sandbox";
|
|
71
|
+
|
|
72
|
+
export default {
|
|
73
|
+
async fetch(request: Request, env: Env) {
|
|
74
|
+
// Your worker code here
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Basic Usage
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { cloudflare } from '@computesdk/cloudflare';
|
|
83
|
+
|
|
84
|
+
// Initialize the provider with your Durable Object binding
|
|
85
|
+
const provider = cloudflare({
|
|
86
|
+
sandboxBinding: env.Sandbox, // Your Durable Object binding
|
|
87
|
+
runtime: 'python',
|
|
88
|
+
timeout: 300000,
|
|
89
|
+
envVars: {
|
|
90
|
+
MY_VAR: 'hello world'
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Create a sandbox
|
|
95
|
+
const sandbox = await provider.sandbox.create();
|
|
96
|
+
|
|
97
|
+
// Execute Python code
|
|
98
|
+
const result = await sandbox.runCode(`
|
|
99
|
+
import sys
|
|
100
|
+
print(f"Python version: {sys.version}")
|
|
101
|
+
print("Hello from Cloudflare!")
|
|
102
|
+
`);
|
|
103
|
+
|
|
104
|
+
console.log(result.stdout);
|
|
105
|
+
|
|
106
|
+
// Execute shell commands
|
|
107
|
+
const cmdResult = await sandbox.runCommand('ls', ['-la']);
|
|
108
|
+
console.log(cmdResult.stdout);
|
|
109
|
+
|
|
110
|
+
// File operations
|
|
111
|
+
await sandbox.filesystem.writeFile('/tmp/hello.txt', 'Hello Cloudflare!');
|
|
112
|
+
const content = await sandbox.filesystem.readFile('/tmp/hello.txt');
|
|
113
|
+
console.log(content); // "Hello Cloudflare!"
|
|
114
|
+
|
|
115
|
+
// Expose a web service
|
|
116
|
+
await sandbox.runCode(`
|
|
117
|
+
import http.server
|
|
118
|
+
import socketserver
|
|
119
|
+
PORT = 3000
|
|
120
|
+
Handler = http.server.SimpleHTTPRequestHandler
|
|
121
|
+
with socketserver.TCPServer(("", PORT), Handler) as httpd:
|
|
122
|
+
print(f"Server running on port {PORT}")
|
|
123
|
+
httpd.serve_forever()
|
|
124
|
+
`);
|
|
125
|
+
|
|
126
|
+
// Get the public URL
|
|
127
|
+
const url = await sandbox.getUrl({ port: 3000 });
|
|
128
|
+
console.log(`Service available at: ${url}`);
|
|
129
|
+
|
|
130
|
+
// Clean up
|
|
131
|
+
await sandbox.destroy();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Advanced Usage
|
|
135
|
+
|
|
136
|
+
### Runtime Detection
|
|
137
|
+
|
|
138
|
+
The provider automatically detects the runtime based on code content:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Automatically detected as Python
|
|
142
|
+
await sandbox.runCode('print("Hello Python")');
|
|
143
|
+
|
|
144
|
+
// Automatically detected as Node.js
|
|
145
|
+
await sandbox.runCode('console.log("Hello Node.js")');
|
|
146
|
+
|
|
147
|
+
// Explicitly specify runtime
|
|
148
|
+
await sandbox.runCode('print("Hello")', 'python');
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Environment Variables
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const provider = cloudflare({
|
|
155
|
+
sandboxBinding: env.Sandbox,
|
|
156
|
+
envVars: {
|
|
157
|
+
API_KEY: 'your-api-key',
|
|
158
|
+
DATABASE_URL: 'postgresql://localhost:5432/mydb',
|
|
159
|
+
NODE_ENV: 'production'
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Port Forwarding
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Start a web server
|
|
168
|
+
await sandbox.runCode(`
|
|
169
|
+
const express = require('express');
|
|
170
|
+
const app = express();
|
|
171
|
+
app.get('/', (req, res) => res.json({ message: 'Hello from Cloudflare!' }));
|
|
172
|
+
app.listen(8080);
|
|
173
|
+
`);
|
|
174
|
+
|
|
175
|
+
// Get public URL
|
|
176
|
+
const url = await sandbox.getUrl({ port: 8080, protocol: 'https' });
|
|
177
|
+
console.log(`API available at: ${url}`);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### File System Operations
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// Create directories
|
|
184
|
+
await sandbox.filesystem.mkdir('/app');
|
|
185
|
+
|
|
186
|
+
// Write files
|
|
187
|
+
await sandbox.filesystem.writeFile('/app/package.json', JSON.stringify({
|
|
188
|
+
name: 'my-app',
|
|
189
|
+
version: '1.0.0'
|
|
190
|
+
}, null, 2));
|
|
191
|
+
|
|
192
|
+
// Read files
|
|
193
|
+
const packageJson = await sandbox.filesystem.readFile('/app/package.json');
|
|
194
|
+
const config = JSON.parse(packageJson);
|
|
195
|
+
|
|
196
|
+
// List directory contents
|
|
197
|
+
const files = await sandbox.filesystem.readdir('/app');
|
|
198
|
+
files.forEach(file => {
|
|
199
|
+
console.log(`${file.name} (${file.isDirectory ? 'dir' : 'file'})`);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Check if file exists
|
|
203
|
+
if (await sandbox.filesystem.exists('/app/package.json')) {
|
|
204
|
+
console.log('Package.json found!');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Remove files/directories
|
|
208
|
+
await sandbox.filesystem.remove('/app/temp');
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Git Operations
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Clone a repository
|
|
215
|
+
await sandbox.runCode(`
|
|
216
|
+
import subprocess
|
|
217
|
+
result = subprocess.run([
|
|
218
|
+
'git', 'clone', 'https://github.com/user/repo.git', '/app'
|
|
219
|
+
], capture_output=True, text=True)
|
|
220
|
+
print(result.stdout)
|
|
221
|
+
`);
|
|
222
|
+
|
|
223
|
+
// Or using the built-in git functionality (if available)
|
|
224
|
+
const result = await sandbox.runCommand('git', [
|
|
225
|
+
'clone',
|
|
226
|
+
'https://github.com/user/repo.git',
|
|
227
|
+
'/app'
|
|
228
|
+
]);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Configuration Options
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
interface CloudflareConfig {
|
|
235
|
+
/** Cloudflare Sandbox binding from Workers environment (required) */
|
|
236
|
+
sandboxBinding: any;
|
|
237
|
+
|
|
238
|
+
/** Default runtime environment */
|
|
239
|
+
runtime?: 'python' | 'node';
|
|
240
|
+
|
|
241
|
+
/** Execution timeout in milliseconds (default: 300000) */
|
|
242
|
+
timeout?: number;
|
|
243
|
+
|
|
244
|
+
/** Environment variables to pass to sandbox */
|
|
245
|
+
envVars?: Record<string, string>;
|
|
246
|
+
|
|
247
|
+
/** Base URL for preview URLs (defaults to worker domain) */
|
|
248
|
+
baseUrl?: string;
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Error Handling
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
try {
|
|
256
|
+
const result = await sandbox.runCode('invalid python syntax');
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (error.message.includes('Syntax error')) {
|
|
259
|
+
console.log('Code has syntax errors');
|
|
260
|
+
} else {
|
|
261
|
+
console.log('Execution failed:', error.message);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Limitations
|
|
267
|
+
|
|
268
|
+
- Requires Cloudflare Workers environment with Durable Objects
|
|
269
|
+
- Container setup currently requires Docker configuration
|
|
270
|
+
- Resource limits apply based on your Cloudflare plan
|
|
271
|
+
- Some system calls may be restricted in the container environment
|
|
272
|
+
|
|
273
|
+
## Examples
|
|
274
|
+
|
|
275
|
+
Check out the [examples directory](../../examples) for complete working examples:
|
|
276
|
+
|
|
277
|
+
- **Basic Usage**: Simple code execution
|
|
278
|
+
- **Web Server**: Express.js app with public URLs
|
|
279
|
+
- **Data Processing**: Python data analysis with file I/O
|
|
280
|
+
- **CI/CD**: Automated testing and building
|
|
281
|
+
|
|
282
|
+
## License
|
|
283
|
+
|
|
284
|
+
MIT
|
|
285
|
+
|
|
286
|
+
## Contributing
|
|
287
|
+
|
|
288
|
+
See the [main repository](https://github.com/computesdk/computesdk) for contribution guidelines.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as computesdk from 'computesdk';
|
|
2
|
+
import { Runtime } from 'computesdk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cloudflare-specific configuration options
|
|
6
|
+
*/
|
|
7
|
+
interface CloudflareConfig {
|
|
8
|
+
/** Cloudflare Sandbox binding from Workers environment - the Durable Object binding */
|
|
9
|
+
sandboxBinding?: any;
|
|
10
|
+
/** Default runtime environment */
|
|
11
|
+
runtime?: Runtime;
|
|
12
|
+
/** Execution timeout in milliseconds */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
/** Environment variables to pass to sandbox */
|
|
15
|
+
envVars?: Record<string, string>;
|
|
16
|
+
/** Base URL for preview URLs (defaults to worker domain) */
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Cloudflare sandbox wrapper - wraps the Cloudflare Sandbox instance
|
|
21
|
+
*/
|
|
22
|
+
interface CloudflareSandbox {
|
|
23
|
+
sandbox: any;
|
|
24
|
+
sandboxId: string;
|
|
25
|
+
exposedPorts: Map<number, string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a Cloudflare provider instance using the factory pattern
|
|
29
|
+
*/
|
|
30
|
+
declare const cloudflare: (config: CloudflareConfig) => computesdk.Provider<CloudflareSandbox, any, any>;
|
|
31
|
+
|
|
32
|
+
export { type CloudflareConfig, cloudflare };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as computesdk from 'computesdk';
|
|
2
|
+
import { Runtime } from 'computesdk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cloudflare-specific configuration options
|
|
6
|
+
*/
|
|
7
|
+
interface CloudflareConfig {
|
|
8
|
+
/** Cloudflare Sandbox binding from Workers environment - the Durable Object binding */
|
|
9
|
+
sandboxBinding?: any;
|
|
10
|
+
/** Default runtime environment */
|
|
11
|
+
runtime?: Runtime;
|
|
12
|
+
/** Execution timeout in milliseconds */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
/** Environment variables to pass to sandbox */
|
|
15
|
+
envVars?: Record<string, string>;
|
|
16
|
+
/** Base URL for preview URLs (defaults to worker domain) */
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Cloudflare sandbox wrapper - wraps the Cloudflare Sandbox instance
|
|
21
|
+
*/
|
|
22
|
+
interface CloudflareSandbox {
|
|
23
|
+
sandbox: any;
|
|
24
|
+
sandboxId: string;
|
|
25
|
+
exposedPorts: Map<number, string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a Cloudflare provider instance using the factory pattern
|
|
29
|
+
*/
|
|
30
|
+
declare const cloudflare: (config: CloudflareConfig) => computesdk.Provider<CloudflareSandbox, any, any>;
|
|
31
|
+
|
|
32
|
+
export { type CloudflareConfig, cloudflare };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
cloudflare: () => cloudflare
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var import_sandbox = require("@cloudflare/sandbox");
|
|
27
|
+
var import_computesdk = require("computesdk");
|
|
28
|
+
function detectRuntime(code) {
|
|
29
|
+
if (code.includes("print(") || code.includes("import ") || code.includes("def ") || code.includes("sys.") || code.includes("json.") || code.includes("__") || code.includes('f"') || code.includes("f'") || code.includes("raise ")) {
|
|
30
|
+
return "python";
|
|
31
|
+
}
|
|
32
|
+
if (code.includes("console.log") || code.includes("process.") || code.includes("require(") || code.includes("module.exports") || code.includes("__dirname") || code.includes("__filename")) {
|
|
33
|
+
return "node";
|
|
34
|
+
}
|
|
35
|
+
return "python";
|
|
36
|
+
}
|
|
37
|
+
var cloudflare = (0, import_computesdk.createProvider)({
|
|
38
|
+
name: "cloudflare",
|
|
39
|
+
methods: {
|
|
40
|
+
sandbox: {
|
|
41
|
+
// Collection operations (map to compute.sandbox.*)
|
|
42
|
+
create: async (config, options) => {
|
|
43
|
+
if (!config.sandboxBinding) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Missing Cloudflare Sandbox binding. Provide 'sandboxBinding' in config with the Durable Object binding from your Cloudflare Workers environment (env.Sandbox). See https://developers.cloudflare.com/durable-objects/get-started/ for setup instructions.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const sandboxId = options?.sandboxId || `cf-sandbox-${Date.now()}`;
|
|
49
|
+
try {
|
|
50
|
+
const sandbox = (0, import_sandbox.getSandbox)(config.sandboxBinding, sandboxId);
|
|
51
|
+
if (config.envVars) {
|
|
52
|
+
await sandbox.setEnvVars(config.envVars);
|
|
53
|
+
}
|
|
54
|
+
const cloudflareSandbox = {
|
|
55
|
+
sandbox,
|
|
56
|
+
sandboxId,
|
|
57
|
+
exposedPorts: /* @__PURE__ */ new Map()
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
sandbox: cloudflareSandbox,
|
|
61
|
+
sandboxId
|
|
62
|
+
};
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error instanceof Error) {
|
|
65
|
+
if (error.message.includes("unauthorized") || error.message.includes("binding")) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Cloudflare Sandbox binding failed. Ensure your Durable Object binding is properly configured in wrangler.toml. See https://developers.cloudflare.com/durable-objects/get-started/ for setup instructions.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (error.message.includes("quota") || error.message.includes("limit")) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Cloudflare resource limits exceeded. Check your usage at https://dash.cloudflare.com/`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Failed to create Cloudflare sandbox: ${error instanceof Error ? error.message : String(error)}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
getById: async (config, sandboxId) => {
|
|
82
|
+
if (!config.sandboxBinding) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const sandbox = (0, import_sandbox.getSandbox)(config.sandboxBinding, sandboxId);
|
|
87
|
+
try {
|
|
88
|
+
await sandbox.ping();
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
const cloudflareSandbox = {
|
|
92
|
+
sandbox,
|
|
93
|
+
sandboxId,
|
|
94
|
+
exposedPorts: /* @__PURE__ */ new Map()
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
sandbox: cloudflareSandbox,
|
|
98
|
+
sandboxId
|
|
99
|
+
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
list: async (_config) => {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Cloudflare provider does not support listing sandboxes. Cloudflare sandboxes are managed through Durable Objects and don't have a native list API. Use getById to reconnect to specific sandboxes by ID, or implement your own tracking system.`
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
destroy: async (config, sandboxId) => {
|
|
110
|
+
try {
|
|
111
|
+
if (config.sandboxBinding) {
|
|
112
|
+
const sandbox = (0, import_sandbox.getSandbox)(config.sandboxBinding, sandboxId);
|
|
113
|
+
await sandbox.killAllProcesses();
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
// Instance operations (map to individual Sandbox methods)
|
|
119
|
+
runCode: async (cloudflareSandbox, code, runtime) => {
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
try {
|
|
122
|
+
const { sandbox, sandboxId } = cloudflareSandbox;
|
|
123
|
+
const detectedRuntime = runtime || detectRuntime(code);
|
|
124
|
+
let result;
|
|
125
|
+
if (detectedRuntime === "python") {
|
|
126
|
+
const execution = await sandbox.runCode(code, { language: "python" });
|
|
127
|
+
let stdout = "";
|
|
128
|
+
let stderr = "";
|
|
129
|
+
if (execution.results && Array.isArray(execution.results)) {
|
|
130
|
+
for (const res of execution.results) {
|
|
131
|
+
if (res.text) {
|
|
132
|
+
stdout += res.text;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
result = {
|
|
137
|
+
stdout,
|
|
138
|
+
stderr,
|
|
139
|
+
exitCode: 0,
|
|
140
|
+
// Cloudflare code interpreter doesn't expose exit codes directly
|
|
141
|
+
executionTime: Date.now() - startTime,
|
|
142
|
+
sandboxId,
|
|
143
|
+
provider: "cloudflare"
|
|
144
|
+
};
|
|
145
|
+
} else {
|
|
146
|
+
const execResult = await sandbox.exec(`node -e "${code.replace(/"/g, '\\"')}"`);
|
|
147
|
+
result = {
|
|
148
|
+
stdout: execResult.stdout || "",
|
|
149
|
+
stderr: execResult.stderr || "",
|
|
150
|
+
exitCode: execResult.exitCode || 0,
|
|
151
|
+
executionTime: Date.now() - startTime,
|
|
152
|
+
sandboxId,
|
|
153
|
+
provider: "cloudflare"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (result.stderr && (result.stderr.includes("SyntaxError") || result.stderr.includes("invalid syntax") || result.stderr.includes("Unexpected token"))) {
|
|
157
|
+
throw new Error(`Syntax error: ${result.stderr.trim()}`);
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error instanceof Error && error.message.includes("Syntax error")) {
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Cloudflare execution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
runCommand: async (cloudflareSandbox, command, args = []) => {
|
|
170
|
+
const startTime = Date.now();
|
|
171
|
+
try {
|
|
172
|
+
const { sandbox, sandboxId } = cloudflareSandbox;
|
|
173
|
+
const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
174
|
+
const execResult = await sandbox.exec(fullCommand);
|
|
175
|
+
return {
|
|
176
|
+
stdout: execResult.stdout || "",
|
|
177
|
+
stderr: execResult.stderr || "",
|
|
178
|
+
exitCode: execResult.exitCode || 0,
|
|
179
|
+
executionTime: Date.now() - startTime,
|
|
180
|
+
sandboxId,
|
|
181
|
+
provider: "cloudflare"
|
|
182
|
+
};
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return {
|
|
185
|
+
stdout: "",
|
|
186
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
187
|
+
exitCode: 127,
|
|
188
|
+
// Command not found exit code
|
|
189
|
+
executionTime: Date.now() - startTime,
|
|
190
|
+
sandboxId: cloudflareSandbox.sandboxId,
|
|
191
|
+
provider: "cloudflare"
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
getInfo: async (cloudflareSandbox) => {
|
|
196
|
+
try {
|
|
197
|
+
const { sandbox, sandboxId } = cloudflareSandbox;
|
|
198
|
+
try {
|
|
199
|
+
await sandbox.ping();
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
id: sandboxId,
|
|
204
|
+
provider: "cloudflare",
|
|
205
|
+
runtime: "python",
|
|
206
|
+
// Cloudflare default
|
|
207
|
+
status: "running",
|
|
208
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
209
|
+
timeout: 3e5,
|
|
210
|
+
metadata: {
|
|
211
|
+
cloudflareSandboxId: sandboxId,
|
|
212
|
+
durableObjectSandbox: true
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
id: cloudflareSandbox.sandboxId,
|
|
218
|
+
provider: "cloudflare",
|
|
219
|
+
runtime: "python",
|
|
220
|
+
status: "error",
|
|
221
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
222
|
+
timeout: 3e5,
|
|
223
|
+
metadata: {
|
|
224
|
+
cloudflareSandboxId: cloudflareSandbox.sandboxId,
|
|
225
|
+
durableObjectSandbox: true,
|
|
226
|
+
error: error instanceof Error ? error.message : String(error)
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
getUrl: async (cloudflareSandbox, options) => {
|
|
232
|
+
try {
|
|
233
|
+
const { sandbox, exposedPorts } = cloudflareSandbox;
|
|
234
|
+
const { port, protocol = "https" } = options;
|
|
235
|
+
if (exposedPorts.has(port)) {
|
|
236
|
+
return exposedPorts.get(port);
|
|
237
|
+
}
|
|
238
|
+
const preview = await sandbox.exposePort(port);
|
|
239
|
+
const url = `${protocol}://${preview.url}`;
|
|
240
|
+
exposedPorts.set(port, url);
|
|
241
|
+
return url;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Failed to expose port ${options.port}: ${error instanceof Error ? error.message : String(error)}`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
// Filesystem methods - Cloudflare has full filesystem support
|
|
249
|
+
filesystem: {
|
|
250
|
+
readFile: async (cloudflareSandbox, path) => {
|
|
251
|
+
try {
|
|
252
|
+
const { sandbox } = cloudflareSandbox;
|
|
253
|
+
const file = await sandbox.readFile(path);
|
|
254
|
+
return file.content || "";
|
|
255
|
+
} catch (error) {
|
|
256
|
+
throw new Error(`Failed to read file ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
writeFile: async (cloudflareSandbox, path, content) => {
|
|
260
|
+
try {
|
|
261
|
+
const { sandbox } = cloudflareSandbox;
|
|
262
|
+
await sandbox.writeFile(path, content);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
throw new Error(`Failed to write file ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
mkdir: async (cloudflareSandbox, path) => {
|
|
268
|
+
try {
|
|
269
|
+
const { sandbox } = cloudflareSandbox;
|
|
270
|
+
await sandbox.mkdir(path);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
throw new Error(`Failed to create directory ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
readdir: async (cloudflareSandbox, path) => {
|
|
276
|
+
try {
|
|
277
|
+
const { sandbox } = cloudflareSandbox;
|
|
278
|
+
const result = await sandbox.exec(`ls -la "${path}"`);
|
|
279
|
+
if (result.exitCode !== 0) {
|
|
280
|
+
throw new Error(`Directory listing failed: ${result.stderr}`);
|
|
281
|
+
}
|
|
282
|
+
const lines = result.stdout.split("\n").filter((line) => line.trim() && !line.startsWith("total"));
|
|
283
|
+
return lines.map((line) => {
|
|
284
|
+
const parts = line.trim().split(/\s+/);
|
|
285
|
+
const permissions = parts[0] || "";
|
|
286
|
+
const size = parseInt(parts[4]) || 0;
|
|
287
|
+
const dateStr = (parts[5] || "") + " " + (parts[6] || "");
|
|
288
|
+
const date = dateStr.trim() ? new Date(dateStr) : /* @__PURE__ */ new Date();
|
|
289
|
+
const name = parts.slice(8).join(" ") || parts[parts.length - 1] || "unknown";
|
|
290
|
+
return {
|
|
291
|
+
name,
|
|
292
|
+
path: `${path}/${name}`.replace("//", "/"),
|
|
293
|
+
isDirectory: permissions.startsWith("d"),
|
|
294
|
+
size,
|
|
295
|
+
lastModified: isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
throw new Error(`Failed to read directory ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
exists: async (cloudflareSandbox, path) => {
|
|
303
|
+
try {
|
|
304
|
+
const { sandbox } = cloudflareSandbox;
|
|
305
|
+
const result = await sandbox.exec(`test -e "${path}"`);
|
|
306
|
+
return result.exitCode === 0;
|
|
307
|
+
} catch (error) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
remove: async (cloudflareSandbox, path) => {
|
|
312
|
+
try {
|
|
313
|
+
const { sandbox } = cloudflareSandbox;
|
|
314
|
+
const result = await sandbox.exec(`rm -rf "${path}"`);
|
|
315
|
+
if (result.exitCode !== 0) {
|
|
316
|
+
throw new Error(`Remove failed: ${result.stderr}`);
|
|
317
|
+
}
|
|
318
|
+
} catch (error) {
|
|
319
|
+
throw new Error(`Failed to remove ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
327
|
+
0 && (module.exports = {
|
|
328
|
+
cloudflare
|
|
329
|
+
});
|
|
330
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Cloudflare Provider - Factory-based Implementation\n * \n * Full-featured provider using Cloudflare Sandbox SDK with all required methods.\n * Leverages Cloudflare's edge network and Durable Objects for sandboxed execution.\n */\n\nimport { getSandbox } from '@cloudflare/sandbox';\nimport { createProvider } from 'computesdk';\nimport type { \n ExecutionResult, \n SandboxInfo, \n Runtime,\n CreateSandboxOptions,\n FileEntry\n} from 'computesdk';\n\n/**\n * Cloudflare-specific configuration options\n */\nexport interface CloudflareConfig {\n /** Cloudflare Sandbox binding from Workers environment - the Durable Object binding */\n sandboxBinding?: any;\n /** Default runtime environment */\n runtime?: Runtime;\n /** Execution timeout in milliseconds */\n timeout?: number;\n /** Environment variables to pass to sandbox */\n envVars?: Record<string, string>;\n /** Base URL for preview URLs (defaults to worker domain) */\n baseUrl?: string;\n}\n\n/**\n * Cloudflare sandbox wrapper - wraps the Cloudflare Sandbox instance\n */\ninterface CloudflareSandbox {\n sandbox: any; // The actual Cloudflare Sandbox instance from getSandbox()\n sandboxId: string;\n exposedPorts: Map<number, string>; // Track exposed ports and their URLs\n}\n\n/**\n * Detect runtime from code content\n */\nfunction detectRuntime(code: string): Runtime {\n // Strong Python indicators\n if (code.includes('print(') || \n code.includes('import ') ||\n code.includes('def ') ||\n code.includes('sys.') ||\n code.includes('json.') ||\n code.includes('__') ||\n code.includes('f\"') ||\n code.includes(\"f'\") ||\n code.includes('raise ')) {\n return 'python';\n }\n\n // Strong Node.js indicators\n if (code.includes('console.log') || \n code.includes('process.') ||\n code.includes('require(') ||\n code.includes('module.exports') ||\n code.includes('__dirname') ||\n code.includes('__filename')) {\n return 'node';\n }\n\n // Default to Python for Cloudflare (matches their examples)\n return 'python';\n}\n\n/**\n * Create a Cloudflare provider instance using the factory pattern\n */\nexport const cloudflare = createProvider<CloudflareSandbox, CloudflareConfig>({\n name: 'cloudflare',\n methods: {\n sandbox: {\n // Collection operations (map to compute.sandbox.*)\n create: async (config: CloudflareConfig, options?: CreateSandboxOptions) => {\n // Validate Cloudflare Workers environment binding\n if (!config.sandboxBinding) {\n throw new Error(\n `Missing Cloudflare Sandbox binding. Provide 'sandboxBinding' in config with the Durable Object binding from your Cloudflare Workers environment (env.Sandbox). ` +\n `See https://developers.cloudflare.com/durable-objects/get-started/ for setup instructions.`\n );\n }\n\n const sandboxId = options?.sandboxId || `cf-sandbox-${Date.now()}`;\n\n try {\n // Create or connect to Cloudflare sandbox using getSandbox\n const sandbox = getSandbox(config.sandboxBinding, sandboxId);\n\n // Set environment variables if provided\n if (config.envVars) {\n await sandbox.setEnvVars(config.envVars);\n }\n\n const cloudflareSandbox: CloudflareSandbox = {\n sandbox,\n sandboxId,\n exposedPorts: new Map()\n };\n\n return {\n sandbox: cloudflareSandbox,\n sandboxId\n };\n } catch (error) {\n if (error instanceof Error) {\n if (error.message.includes('unauthorized') || error.message.includes('binding')) {\n throw new Error(\n `Cloudflare Sandbox binding failed. Ensure your Durable Object binding is properly configured in wrangler.toml. ` +\n `See https://developers.cloudflare.com/durable-objects/get-started/ for setup instructions.`\n );\n }\n if (error.message.includes('quota') || error.message.includes('limit')) {\n throw new Error(\n `Cloudflare resource limits exceeded. Check your usage at https://dash.cloudflare.com/`\n );\n }\n }\n throw new Error(\n `Failed to create Cloudflare sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: CloudflareConfig, sandboxId: string) => {\n if (!config.sandboxBinding) {\n return null;\n }\n\n try {\n // Reconnect to existing Cloudflare sandbox\n const sandbox = getSandbox(config.sandboxBinding, sandboxId);\n\n // Test connection - Note: ping may not be available on all sandbox versions\n try {\n await (sandbox as any).ping();\n } catch {\n // ping not supported, continue\n }\n\n const cloudflareSandbox: CloudflareSandbox = {\n sandbox,\n sandboxId,\n exposedPorts: new Map()\n };\n\n return {\n sandbox: cloudflareSandbox,\n sandboxId\n };\n } catch (error) {\n // Sandbox doesn't exist or can't be accessed\n return null;\n }\n },\n\n list: async (_config: CloudflareConfig) => {\n throw new Error(\n `Cloudflare provider does not support listing sandboxes. Cloudflare sandboxes are managed through Durable Objects and don't have a native list API. ` +\n `Use getById to reconnect to specific sandboxes by ID, or implement your own tracking system.`\n );\n },\n\n destroy: async (config: CloudflareConfig, sandboxId: string) => {\n try {\n if (config.sandboxBinding) {\n const sandbox = getSandbox(config.sandboxBinding, sandboxId);\n \n // Stop all processes and clean up\n await sandbox.killAllProcesses();\n \n // Note: Cloudflare Durable Objects manage their own lifecycle\n // The actual destruction happens automatically when the object is no longer referenced\n }\n } catch (error) {\n // Sandbox might already be destroyed or doesn't exist\n // This is acceptable for destroy operations\n }\n },\n\n // Instance operations (map to individual Sandbox methods)\n runCode: async (cloudflareSandbox: CloudflareSandbox, code: string, runtime?: Runtime): Promise<ExecutionResult> => {\n const startTime = Date.now();\n\n try {\n const { sandbox, sandboxId } = cloudflareSandbox;\n \n // Auto-detect runtime from code if not specified\n const detectedRuntime = runtime || detectRuntime(code);\n \n let result;\n\n if (detectedRuntime === 'python') {\n // Use Cloudflare's code interpreter for Python\n const execution = await sandbox.runCode(code, { language: 'python' });\n \n // Process the execution result\n let stdout = '';\n let stderr = '';\n \n // Handle streaming results if available\n if (execution.results && Array.isArray(execution.results)) {\n for (const res of execution.results) {\n if (res.text) {\n stdout += res.text;\n }\n }\n }\n \n result = {\n stdout,\n stderr,\n exitCode: 0, // Cloudflare code interpreter doesn't expose exit codes directly\n executionTime: Date.now() - startTime,\n sandboxId,\n provider: 'cloudflare'\n };\n } else {\n // For Node.js/JavaScript, use exec with node command\n const execResult = await sandbox.exec(`node -e \"${code.replace(/\"/g, '\\\\\"')}\"`);\n \n result = {\n stdout: execResult.stdout || '',\n stderr: execResult.stderr || '',\n exitCode: execResult.exitCode || 0,\n executionTime: Date.now() - startTime,\n sandboxId,\n provider: 'cloudflare'\n };\n }\n\n // Check for syntax errors\n if (result.stderr && (\n result.stderr.includes('SyntaxError') ||\n result.stderr.includes('invalid syntax') ||\n result.stderr.includes('Unexpected token')\n )) {\n throw new Error(`Syntax error: ${result.stderr.trim()}`);\n }\n\n return result;\n } catch (error) {\n // Re-throw syntax errors\n if (error instanceof Error && error.message.includes('Syntax error')) {\n throw error;\n }\n \n throw new Error(\n `Cloudflare execution failed: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n runCommand: async (cloudflareSandbox: CloudflareSandbox, command: string, args: string[] = []): Promise<ExecutionResult> => {\n const startTime = Date.now();\n\n try {\n const { sandbox, sandboxId } = cloudflareSandbox;\n \n // Construct full command with arguments\n const fullCommand = args.length > 0 ? `${command} ${args.join(' ')}` : command;\n\n // Execute command using Cloudflare's exec method\n const execResult = await sandbox.exec(fullCommand);\n\n return {\n stdout: execResult.stdout || '',\n stderr: execResult.stderr || '',\n exitCode: execResult.exitCode || 0,\n executionTime: Date.now() - startTime,\n sandboxId,\n provider: 'cloudflare'\n };\n } catch (error) {\n // For command failures, return error info instead of throwing\n return {\n stdout: '',\n stderr: error instanceof Error ? error.message : String(error),\n exitCode: 127, // Command not found exit code\n executionTime: Date.now() - startTime,\n sandboxId: cloudflareSandbox.sandboxId,\n provider: 'cloudflare'\n };\n }\n },\n\n getInfo: async (cloudflareSandbox: CloudflareSandbox): Promise<SandboxInfo> => {\n try {\n const { sandbox, sandboxId } = cloudflareSandbox;\n \n // Test if sandbox is still alive - ping may not be available\n try {\n await (sandbox as any).ping();\n } catch {\n // ping not supported, continue\n }\n\n return {\n id: sandboxId,\n provider: 'cloudflare',\n runtime: 'python', // Cloudflare default\n status: 'running',\n createdAt: new Date(),\n timeout: 300000,\n metadata: {\n cloudflareSandboxId: sandboxId,\n durableObjectSandbox: true\n }\n };\n } catch (error) {\n return {\n id: cloudflareSandbox.sandboxId,\n provider: 'cloudflare',\n runtime: 'python',\n status: 'error',\n createdAt: new Date(),\n timeout: 300000,\n metadata: {\n cloudflareSandboxId: cloudflareSandbox.sandboxId,\n durableObjectSandbox: true,\n error: error instanceof Error ? error.message : String(error)\n }\n };\n }\n },\n\n getUrl: async (cloudflareSandbox: CloudflareSandbox, options: { port: number; protocol?: string }): Promise<string> => {\n try {\n const { sandbox, exposedPorts } = cloudflareSandbox;\n const { port, protocol = 'https' } = options;\n\n // Check if port is already exposed\n if (exposedPorts.has(port)) {\n return exposedPorts.get(port)!;\n }\n\n // Expose the port using Cloudflare's exposePort method\n const preview = await sandbox.exposePort(port);\n const url = `${protocol}://${preview.url}`;\n \n // Cache the exposed URL\n exposedPorts.set(port, url);\n \n return url;\n } catch (error) {\n throw new Error(\n `Failed to expose port ${options.port}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n // Filesystem methods - Cloudflare has full filesystem support\n filesystem: {\n readFile: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<string> => {\n try {\n const { sandbox } = cloudflareSandbox;\n const file = await sandbox.readFile(path);\n return file.content || '';\n } catch (error) {\n throw new Error(`Failed to read file ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n writeFile: async (cloudflareSandbox: CloudflareSandbox, path: string, content: string): Promise<void> => {\n try {\n const { sandbox } = cloudflareSandbox;\n await sandbox.writeFile(path, content);\n } catch (error) {\n throw new Error(`Failed to write file ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n mkdir: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<void> => {\n try {\n const { sandbox } = cloudflareSandbox;\n await sandbox.mkdir(path);\n } catch (error) {\n throw new Error(`Failed to create directory ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n readdir: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<FileEntry[]> => {\n try {\n const { sandbox } = cloudflareSandbox;\n \n // Use ls command to get directory listing\n const result = await sandbox.exec(`ls -la \"${path}\"`);\n \n if (result.exitCode !== 0) {\n throw new Error(`Directory listing failed: ${result.stderr}`);\n }\n\n const lines = result.stdout.split('\\n').filter((line: string) => line.trim() && !line.startsWith('total'));\n\n return lines.map((line: string) => {\n const parts = line.trim().split(/\\s+/);\n const permissions = parts[0] || '';\n const size = parseInt(parts[4]) || 0;\n const dateStr = (parts[5] || '') + ' ' + (parts[6] || '');\n const date = dateStr.trim() ? new Date(dateStr) : new Date();\n const name = parts.slice(8).join(' ') || parts[parts.length - 1] || 'unknown';\n\n return {\n name,\n path: `${path}/${name}`.replace('//', '/'),\n isDirectory: permissions.startsWith('d'),\n size,\n lastModified: isNaN(date.getTime()) ? new Date() : date\n };\n });\n } catch (error) {\n throw new Error(`Failed to read directory ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n exists: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<boolean> => {\n try {\n const { sandbox } = cloudflareSandbox;\n const result = await sandbox.exec(`test -e \"${path}\"`);\n return result.exitCode === 0;\n } catch (error) {\n return false;\n }\n },\n\n remove: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<void> => {\n try {\n const { sandbox } = cloudflareSandbox;\n const result = await sandbox.exec(`rm -rf \"${path}\"`);\n \n if (result.exitCode !== 0) {\n throw new Error(`Remove failed: ${result.stderr}`);\n }\n } catch (error) {\n throw new Error(`Failed to remove ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n }\n }\n }\n});"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { getSandbox } from "@cloudflare/sandbox";
|
|
3
|
+
import { createProvider } from "computesdk";
|
|
4
|
+
function detectRuntime(code) {
|
|
5
|
+
if (code.includes("print(") || code.includes("import ") || code.includes("def ") || code.includes("sys.") || code.includes("json.") || code.includes("__") || code.includes('f"') || code.includes("f'") || code.includes("raise ")) {
|
|
6
|
+
return "python";
|
|
7
|
+
}
|
|
8
|
+
if (code.includes("console.log") || code.includes("process.") || code.includes("require(") || code.includes("module.exports") || code.includes("__dirname") || code.includes("__filename")) {
|
|
9
|
+
return "node";
|
|
10
|
+
}
|
|
11
|
+
return "python";
|
|
12
|
+
}
|
|
13
|
+
var cloudflare = createProvider({
|
|
14
|
+
name: "cloudflare",
|
|
15
|
+
methods: {
|
|
16
|
+
sandbox: {
|
|
17
|
+
// Collection operations (map to compute.sandbox.*)
|
|
18
|
+
create: async (config, options) => {
|
|
19
|
+
if (!config.sandboxBinding) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Missing Cloudflare Sandbox binding. Provide 'sandboxBinding' in config with the Durable Object binding from your Cloudflare Workers environment (env.Sandbox). See https://developers.cloudflare.com/durable-objects/get-started/ for setup instructions.`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const sandboxId = options?.sandboxId || `cf-sandbox-${Date.now()}`;
|
|
25
|
+
try {
|
|
26
|
+
const sandbox = getSandbox(config.sandboxBinding, sandboxId);
|
|
27
|
+
if (config.envVars) {
|
|
28
|
+
await sandbox.setEnvVars(config.envVars);
|
|
29
|
+
}
|
|
30
|
+
const cloudflareSandbox = {
|
|
31
|
+
sandbox,
|
|
32
|
+
sandboxId,
|
|
33
|
+
exposedPorts: /* @__PURE__ */ new Map()
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
sandbox: cloudflareSandbox,
|
|
37
|
+
sandboxId
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error instanceof Error) {
|
|
41
|
+
if (error.message.includes("unauthorized") || error.message.includes("binding")) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Cloudflare Sandbox binding failed. Ensure your Durable Object binding is properly configured in wrangler.toml. See https://developers.cloudflare.com/durable-objects/get-started/ for setup instructions.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (error.message.includes("quota") || error.message.includes("limit")) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Cloudflare resource limits exceeded. Check your usage at https://dash.cloudflare.com/`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Failed to create Cloudflare sandbox: ${error instanceof Error ? error.message : String(error)}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
getById: async (config, sandboxId) => {
|
|
58
|
+
if (!config.sandboxBinding) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const sandbox = getSandbox(config.sandboxBinding, sandboxId);
|
|
63
|
+
try {
|
|
64
|
+
await sandbox.ping();
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
const cloudflareSandbox = {
|
|
68
|
+
sandbox,
|
|
69
|
+
sandboxId,
|
|
70
|
+
exposedPorts: /* @__PURE__ */ new Map()
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
sandbox: cloudflareSandbox,
|
|
74
|
+
sandboxId
|
|
75
|
+
};
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
list: async (_config) => {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Cloudflare provider does not support listing sandboxes. Cloudflare sandboxes are managed through Durable Objects and don't have a native list API. Use getById to reconnect to specific sandboxes by ID, or implement your own tracking system.`
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
destroy: async (config, sandboxId) => {
|
|
86
|
+
try {
|
|
87
|
+
if (config.sandboxBinding) {
|
|
88
|
+
const sandbox = getSandbox(config.sandboxBinding, sandboxId);
|
|
89
|
+
await sandbox.killAllProcesses();
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
// Instance operations (map to individual Sandbox methods)
|
|
95
|
+
runCode: async (cloudflareSandbox, code, runtime) => {
|
|
96
|
+
const startTime = Date.now();
|
|
97
|
+
try {
|
|
98
|
+
const { sandbox, sandboxId } = cloudflareSandbox;
|
|
99
|
+
const detectedRuntime = runtime || detectRuntime(code);
|
|
100
|
+
let result;
|
|
101
|
+
if (detectedRuntime === "python") {
|
|
102
|
+
const execution = await sandbox.runCode(code, { language: "python" });
|
|
103
|
+
let stdout = "";
|
|
104
|
+
let stderr = "";
|
|
105
|
+
if (execution.results && Array.isArray(execution.results)) {
|
|
106
|
+
for (const res of execution.results) {
|
|
107
|
+
if (res.text) {
|
|
108
|
+
stdout += res.text;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
result = {
|
|
113
|
+
stdout,
|
|
114
|
+
stderr,
|
|
115
|
+
exitCode: 0,
|
|
116
|
+
// Cloudflare code interpreter doesn't expose exit codes directly
|
|
117
|
+
executionTime: Date.now() - startTime,
|
|
118
|
+
sandboxId,
|
|
119
|
+
provider: "cloudflare"
|
|
120
|
+
};
|
|
121
|
+
} else {
|
|
122
|
+
const execResult = await sandbox.exec(`node -e "${code.replace(/"/g, '\\"')}"`);
|
|
123
|
+
result = {
|
|
124
|
+
stdout: execResult.stdout || "",
|
|
125
|
+
stderr: execResult.stderr || "",
|
|
126
|
+
exitCode: execResult.exitCode || 0,
|
|
127
|
+
executionTime: Date.now() - startTime,
|
|
128
|
+
sandboxId,
|
|
129
|
+
provider: "cloudflare"
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (result.stderr && (result.stderr.includes("SyntaxError") || result.stderr.includes("invalid syntax") || result.stderr.includes("Unexpected token"))) {
|
|
133
|
+
throw new Error(`Syntax error: ${result.stderr.trim()}`);
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error instanceof Error && error.message.includes("Syntax error")) {
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Cloudflare execution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
runCommand: async (cloudflareSandbox, command, args = []) => {
|
|
146
|
+
const startTime = Date.now();
|
|
147
|
+
try {
|
|
148
|
+
const { sandbox, sandboxId } = cloudflareSandbox;
|
|
149
|
+
const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
150
|
+
const execResult = await sandbox.exec(fullCommand);
|
|
151
|
+
return {
|
|
152
|
+
stdout: execResult.stdout || "",
|
|
153
|
+
stderr: execResult.stderr || "",
|
|
154
|
+
exitCode: execResult.exitCode || 0,
|
|
155
|
+
executionTime: Date.now() - startTime,
|
|
156
|
+
sandboxId,
|
|
157
|
+
provider: "cloudflare"
|
|
158
|
+
};
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return {
|
|
161
|
+
stdout: "",
|
|
162
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
163
|
+
exitCode: 127,
|
|
164
|
+
// Command not found exit code
|
|
165
|
+
executionTime: Date.now() - startTime,
|
|
166
|
+
sandboxId: cloudflareSandbox.sandboxId,
|
|
167
|
+
provider: "cloudflare"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
getInfo: async (cloudflareSandbox) => {
|
|
172
|
+
try {
|
|
173
|
+
const { sandbox, sandboxId } = cloudflareSandbox;
|
|
174
|
+
try {
|
|
175
|
+
await sandbox.ping();
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
id: sandboxId,
|
|
180
|
+
provider: "cloudflare",
|
|
181
|
+
runtime: "python",
|
|
182
|
+
// Cloudflare default
|
|
183
|
+
status: "running",
|
|
184
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
185
|
+
timeout: 3e5,
|
|
186
|
+
metadata: {
|
|
187
|
+
cloudflareSandboxId: sandboxId,
|
|
188
|
+
durableObjectSandbox: true
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return {
|
|
193
|
+
id: cloudflareSandbox.sandboxId,
|
|
194
|
+
provider: "cloudflare",
|
|
195
|
+
runtime: "python",
|
|
196
|
+
status: "error",
|
|
197
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
198
|
+
timeout: 3e5,
|
|
199
|
+
metadata: {
|
|
200
|
+
cloudflareSandboxId: cloudflareSandbox.sandboxId,
|
|
201
|
+
durableObjectSandbox: true,
|
|
202
|
+
error: error instanceof Error ? error.message : String(error)
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
getUrl: async (cloudflareSandbox, options) => {
|
|
208
|
+
try {
|
|
209
|
+
const { sandbox, exposedPorts } = cloudflareSandbox;
|
|
210
|
+
const { port, protocol = "https" } = options;
|
|
211
|
+
if (exposedPorts.has(port)) {
|
|
212
|
+
return exposedPorts.get(port);
|
|
213
|
+
}
|
|
214
|
+
const preview = await sandbox.exposePort(port);
|
|
215
|
+
const url = `${protocol}://${preview.url}`;
|
|
216
|
+
exposedPorts.set(port, url);
|
|
217
|
+
return url;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Failed to expose port ${options.port}: ${error instanceof Error ? error.message : String(error)}`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
// Filesystem methods - Cloudflare has full filesystem support
|
|
225
|
+
filesystem: {
|
|
226
|
+
readFile: async (cloudflareSandbox, path) => {
|
|
227
|
+
try {
|
|
228
|
+
const { sandbox } = cloudflareSandbox;
|
|
229
|
+
const file = await sandbox.readFile(path);
|
|
230
|
+
return file.content || "";
|
|
231
|
+
} catch (error) {
|
|
232
|
+
throw new Error(`Failed to read file ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
writeFile: async (cloudflareSandbox, path, content) => {
|
|
236
|
+
try {
|
|
237
|
+
const { sandbox } = cloudflareSandbox;
|
|
238
|
+
await sandbox.writeFile(path, content);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
throw new Error(`Failed to write file ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
mkdir: async (cloudflareSandbox, path) => {
|
|
244
|
+
try {
|
|
245
|
+
const { sandbox } = cloudflareSandbox;
|
|
246
|
+
await sandbox.mkdir(path);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
throw new Error(`Failed to create directory ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
readdir: async (cloudflareSandbox, path) => {
|
|
252
|
+
try {
|
|
253
|
+
const { sandbox } = cloudflareSandbox;
|
|
254
|
+
const result = await sandbox.exec(`ls -la "${path}"`);
|
|
255
|
+
if (result.exitCode !== 0) {
|
|
256
|
+
throw new Error(`Directory listing failed: ${result.stderr}`);
|
|
257
|
+
}
|
|
258
|
+
const lines = result.stdout.split("\n").filter((line) => line.trim() && !line.startsWith("total"));
|
|
259
|
+
return lines.map((line) => {
|
|
260
|
+
const parts = line.trim().split(/\s+/);
|
|
261
|
+
const permissions = parts[0] || "";
|
|
262
|
+
const size = parseInt(parts[4]) || 0;
|
|
263
|
+
const dateStr = (parts[5] || "") + " " + (parts[6] || "");
|
|
264
|
+
const date = dateStr.trim() ? new Date(dateStr) : /* @__PURE__ */ new Date();
|
|
265
|
+
const name = parts.slice(8).join(" ") || parts[parts.length - 1] || "unknown";
|
|
266
|
+
return {
|
|
267
|
+
name,
|
|
268
|
+
path: `${path}/${name}`.replace("//", "/"),
|
|
269
|
+
isDirectory: permissions.startsWith("d"),
|
|
270
|
+
size,
|
|
271
|
+
lastModified: isNaN(date.getTime()) ? /* @__PURE__ */ new Date() : date
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
} catch (error) {
|
|
275
|
+
throw new Error(`Failed to read directory ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
exists: async (cloudflareSandbox, path) => {
|
|
279
|
+
try {
|
|
280
|
+
const { sandbox } = cloudflareSandbox;
|
|
281
|
+
const result = await sandbox.exec(`test -e "${path}"`);
|
|
282
|
+
return result.exitCode === 0;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
remove: async (cloudflareSandbox, path) => {
|
|
288
|
+
try {
|
|
289
|
+
const { sandbox } = cloudflareSandbox;
|
|
290
|
+
const result = await sandbox.exec(`rm -rf "${path}"`);
|
|
291
|
+
if (result.exitCode !== 0) {
|
|
292
|
+
throw new Error(`Remove failed: ${result.stderr}`);
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
throw new Error(`Failed to remove ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
export {
|
|
303
|
+
cloudflare
|
|
304
|
+
};
|
|
305
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Cloudflare Provider - Factory-based Implementation\n * \n * Full-featured provider using Cloudflare Sandbox SDK with all required methods.\n * Leverages Cloudflare's edge network and Durable Objects for sandboxed execution.\n */\n\nimport { getSandbox } from '@cloudflare/sandbox';\nimport { createProvider } from 'computesdk';\nimport type { \n ExecutionResult, \n SandboxInfo, \n Runtime,\n CreateSandboxOptions,\n FileEntry\n} from 'computesdk';\n\n/**\n * Cloudflare-specific configuration options\n */\nexport interface CloudflareConfig {\n /** Cloudflare Sandbox binding from Workers environment - the Durable Object binding */\n sandboxBinding?: any;\n /** Default runtime environment */\n runtime?: Runtime;\n /** Execution timeout in milliseconds */\n timeout?: number;\n /** Environment variables to pass to sandbox */\n envVars?: Record<string, string>;\n /** Base URL for preview URLs (defaults to worker domain) */\n baseUrl?: string;\n}\n\n/**\n * Cloudflare sandbox wrapper - wraps the Cloudflare Sandbox instance\n */\ninterface CloudflareSandbox {\n sandbox: any; // The actual Cloudflare Sandbox instance from getSandbox()\n sandboxId: string;\n exposedPorts: Map<number, string>; // Track exposed ports and their URLs\n}\n\n/**\n * Detect runtime from code content\n */\nfunction detectRuntime(code: string): Runtime {\n // Strong Python indicators\n if (code.includes('print(') || \n code.includes('import ') ||\n code.includes('def ') ||\n code.includes('sys.') ||\n code.includes('json.') ||\n code.includes('__') ||\n code.includes('f\"') ||\n code.includes(\"f'\") ||\n code.includes('raise ')) {\n return 'python';\n }\n\n // Strong Node.js indicators\n if (code.includes('console.log') || \n code.includes('process.') ||\n code.includes('require(') ||\n code.includes('module.exports') ||\n code.includes('__dirname') ||\n code.includes('__filename')) {\n return 'node';\n }\n\n // Default to Python for Cloudflare (matches their examples)\n return 'python';\n}\n\n/**\n * Create a Cloudflare provider instance using the factory pattern\n */\nexport const cloudflare = createProvider<CloudflareSandbox, CloudflareConfig>({\n name: 'cloudflare',\n methods: {\n sandbox: {\n // Collection operations (map to compute.sandbox.*)\n create: async (config: CloudflareConfig, options?: CreateSandboxOptions) => {\n // Validate Cloudflare Workers environment binding\n if (!config.sandboxBinding) {\n throw new Error(\n `Missing Cloudflare Sandbox binding. Provide 'sandboxBinding' in config with the Durable Object binding from your Cloudflare Workers environment (env.Sandbox). ` +\n `See https://developers.cloudflare.com/durable-objects/get-started/ for setup instructions.`\n );\n }\n\n const sandboxId = options?.sandboxId || `cf-sandbox-${Date.now()}`;\n\n try {\n // Create or connect to Cloudflare sandbox using getSandbox\n const sandbox = getSandbox(config.sandboxBinding, sandboxId);\n\n // Set environment variables if provided\n if (config.envVars) {\n await sandbox.setEnvVars(config.envVars);\n }\n\n const cloudflareSandbox: CloudflareSandbox = {\n sandbox,\n sandboxId,\n exposedPorts: new Map()\n };\n\n return {\n sandbox: cloudflareSandbox,\n sandboxId\n };\n } catch (error) {\n if (error instanceof Error) {\n if (error.message.includes('unauthorized') || error.message.includes('binding')) {\n throw new Error(\n `Cloudflare Sandbox binding failed. Ensure your Durable Object binding is properly configured in wrangler.toml. ` +\n `See https://developers.cloudflare.com/durable-objects/get-started/ for setup instructions.`\n );\n }\n if (error.message.includes('quota') || error.message.includes('limit')) {\n throw new Error(\n `Cloudflare resource limits exceeded. Check your usage at https://dash.cloudflare.com/`\n );\n }\n }\n throw new Error(\n `Failed to create Cloudflare sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: CloudflareConfig, sandboxId: string) => {\n if (!config.sandboxBinding) {\n return null;\n }\n\n try {\n // Reconnect to existing Cloudflare sandbox\n const sandbox = getSandbox(config.sandboxBinding, sandboxId);\n\n // Test connection - Note: ping may not be available on all sandbox versions\n try {\n await (sandbox as any).ping();\n } catch {\n // ping not supported, continue\n }\n\n const cloudflareSandbox: CloudflareSandbox = {\n sandbox,\n sandboxId,\n exposedPorts: new Map()\n };\n\n return {\n sandbox: cloudflareSandbox,\n sandboxId\n };\n } catch (error) {\n // Sandbox doesn't exist or can't be accessed\n return null;\n }\n },\n\n list: async (_config: CloudflareConfig) => {\n throw new Error(\n `Cloudflare provider does not support listing sandboxes. Cloudflare sandboxes are managed through Durable Objects and don't have a native list API. ` +\n `Use getById to reconnect to specific sandboxes by ID, or implement your own tracking system.`\n );\n },\n\n destroy: async (config: CloudflareConfig, sandboxId: string) => {\n try {\n if (config.sandboxBinding) {\n const sandbox = getSandbox(config.sandboxBinding, sandboxId);\n \n // Stop all processes and clean up\n await sandbox.killAllProcesses();\n \n // Note: Cloudflare Durable Objects manage their own lifecycle\n // The actual destruction happens automatically when the object is no longer referenced\n }\n } catch (error) {\n // Sandbox might already be destroyed or doesn't exist\n // This is acceptable for destroy operations\n }\n },\n\n // Instance operations (map to individual Sandbox methods)\n runCode: async (cloudflareSandbox: CloudflareSandbox, code: string, runtime?: Runtime): Promise<ExecutionResult> => {\n const startTime = Date.now();\n\n try {\n const { sandbox, sandboxId } = cloudflareSandbox;\n \n // Auto-detect runtime from code if not specified\n const detectedRuntime = runtime || detectRuntime(code);\n \n let result;\n\n if (detectedRuntime === 'python') {\n // Use Cloudflare's code interpreter for Python\n const execution = await sandbox.runCode(code, { language: 'python' });\n \n // Process the execution result\n let stdout = '';\n let stderr = '';\n \n // Handle streaming results if available\n if (execution.results && Array.isArray(execution.results)) {\n for (const res of execution.results) {\n if (res.text) {\n stdout += res.text;\n }\n }\n }\n \n result = {\n stdout,\n stderr,\n exitCode: 0, // Cloudflare code interpreter doesn't expose exit codes directly\n executionTime: Date.now() - startTime,\n sandboxId,\n provider: 'cloudflare'\n };\n } else {\n // For Node.js/JavaScript, use exec with node command\n const execResult = await sandbox.exec(`node -e \"${code.replace(/\"/g, '\\\\\"')}\"`);\n \n result = {\n stdout: execResult.stdout || '',\n stderr: execResult.stderr || '',\n exitCode: execResult.exitCode || 0,\n executionTime: Date.now() - startTime,\n sandboxId,\n provider: 'cloudflare'\n };\n }\n\n // Check for syntax errors\n if (result.stderr && (\n result.stderr.includes('SyntaxError') ||\n result.stderr.includes('invalid syntax') ||\n result.stderr.includes('Unexpected token')\n )) {\n throw new Error(`Syntax error: ${result.stderr.trim()}`);\n }\n\n return result;\n } catch (error) {\n // Re-throw syntax errors\n if (error instanceof Error && error.message.includes('Syntax error')) {\n throw error;\n }\n \n throw new Error(\n `Cloudflare execution failed: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n runCommand: async (cloudflareSandbox: CloudflareSandbox, command: string, args: string[] = []): Promise<ExecutionResult> => {\n const startTime = Date.now();\n\n try {\n const { sandbox, sandboxId } = cloudflareSandbox;\n \n // Construct full command with arguments\n const fullCommand = args.length > 0 ? `${command} ${args.join(' ')}` : command;\n\n // Execute command using Cloudflare's exec method\n const execResult = await sandbox.exec(fullCommand);\n\n return {\n stdout: execResult.stdout || '',\n stderr: execResult.stderr || '',\n exitCode: execResult.exitCode || 0,\n executionTime: Date.now() - startTime,\n sandboxId,\n provider: 'cloudflare'\n };\n } catch (error) {\n // For command failures, return error info instead of throwing\n return {\n stdout: '',\n stderr: error instanceof Error ? error.message : String(error),\n exitCode: 127, // Command not found exit code\n executionTime: Date.now() - startTime,\n sandboxId: cloudflareSandbox.sandboxId,\n provider: 'cloudflare'\n };\n }\n },\n\n getInfo: async (cloudflareSandbox: CloudflareSandbox): Promise<SandboxInfo> => {\n try {\n const { sandbox, sandboxId } = cloudflareSandbox;\n \n // Test if sandbox is still alive - ping may not be available\n try {\n await (sandbox as any).ping();\n } catch {\n // ping not supported, continue\n }\n\n return {\n id: sandboxId,\n provider: 'cloudflare',\n runtime: 'python', // Cloudflare default\n status: 'running',\n createdAt: new Date(),\n timeout: 300000,\n metadata: {\n cloudflareSandboxId: sandboxId,\n durableObjectSandbox: true\n }\n };\n } catch (error) {\n return {\n id: cloudflareSandbox.sandboxId,\n provider: 'cloudflare',\n runtime: 'python',\n status: 'error',\n createdAt: new Date(),\n timeout: 300000,\n metadata: {\n cloudflareSandboxId: cloudflareSandbox.sandboxId,\n durableObjectSandbox: true,\n error: error instanceof Error ? error.message : String(error)\n }\n };\n }\n },\n\n getUrl: async (cloudflareSandbox: CloudflareSandbox, options: { port: number; protocol?: string }): Promise<string> => {\n try {\n const { sandbox, exposedPorts } = cloudflareSandbox;\n const { port, protocol = 'https' } = options;\n\n // Check if port is already exposed\n if (exposedPorts.has(port)) {\n return exposedPorts.get(port)!;\n }\n\n // Expose the port using Cloudflare's exposePort method\n const preview = await sandbox.exposePort(port);\n const url = `${protocol}://${preview.url}`;\n \n // Cache the exposed URL\n exposedPorts.set(port, url);\n \n return url;\n } catch (error) {\n throw new Error(\n `Failed to expose port ${options.port}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n // Filesystem methods - Cloudflare has full filesystem support\n filesystem: {\n readFile: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<string> => {\n try {\n const { sandbox } = cloudflareSandbox;\n const file = await sandbox.readFile(path);\n return file.content || '';\n } catch (error) {\n throw new Error(`Failed to read file ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n writeFile: async (cloudflareSandbox: CloudflareSandbox, path: string, content: string): Promise<void> => {\n try {\n const { sandbox } = cloudflareSandbox;\n await sandbox.writeFile(path, content);\n } catch (error) {\n throw new Error(`Failed to write file ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n mkdir: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<void> => {\n try {\n const { sandbox } = cloudflareSandbox;\n await sandbox.mkdir(path);\n } catch (error) {\n throw new Error(`Failed to create directory ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n readdir: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<FileEntry[]> => {\n try {\n const { sandbox } = cloudflareSandbox;\n \n // Use ls command to get directory listing\n const result = await sandbox.exec(`ls -la \"${path}\"`);\n \n if (result.exitCode !== 0) {\n throw new Error(`Directory listing failed: ${result.stderr}`);\n }\n\n const lines = result.stdout.split('\\n').filter((line: string) => line.trim() && !line.startsWith('total'));\n\n return lines.map((line: string) => {\n const parts = line.trim().split(/\\s+/);\n const permissions = parts[0] || '';\n const size = parseInt(parts[4]) || 0;\n const dateStr = (parts[5] || '') + ' ' + (parts[6] || '');\n const date = dateStr.trim() ? new Date(dateStr) : new Date();\n const name = parts.slice(8).join(' ') || parts[parts.length - 1] || 'unknown';\n\n return {\n name,\n path: `${path}/${name}`.replace('//', '/'),\n isDirectory: permissions.startsWith('d'),\n size,\n lastModified: isNaN(date.getTime()) ? new Date() : date\n };\n });\n } catch (error) {\n throw new Error(`Failed to read directory ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n exists: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<boolean> => {\n try {\n const { sandbox } = cloudflareSandbox;\n const result = await sandbox.exec(`test -e \"${path}\"`);\n return result.exitCode === 0;\n } catch (error) {\n return false;\n }\n },\n\n remove: async (cloudflareSandbox: CloudflareSandbox, path: string): Promise<void> => {\n try {\n const { sandbox } = cloudflareSandbox;\n const result = await sandbox.exec(`rm -rf \"${path}\"`);\n \n if (result.exitCode !== 0) {\n throw new Error(`Remove failed: ${result.stderr}`);\n }\n } catch (error) {\n throw new Error(`Failed to remove ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n }\n }\n }\n});"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@computesdk/cloudflare",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Cloudflare provider for ComputeSDK",
|
|
5
|
+
"author": "Garrison",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@cloudflare/sandbox": "^0.3.0",
|
|
22
|
+
"computesdk": "1.7.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"cloudflare",
|
|
26
|
+
"sandbox",
|
|
27
|
+
"code-execution",
|
|
28
|
+
"python",
|
|
29
|
+
"javascript",
|
|
30
|
+
"cloud",
|
|
31
|
+
"compute",
|
|
32
|
+
"durable-objects",
|
|
33
|
+
"workers"
|
|
34
|
+
],
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/computesdk/computesdk.git",
|
|
38
|
+
"directory": "packages/cloudflare"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/computesdk/computesdk/tree/main/packages/cloudflare",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/computesdk/computesdk/issues"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@cloudflare/workers-types": "^4.20250826.0",
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"@vitest/coverage-v8": "^1.0.0",
|
|
48
|
+
"eslint": "^8.37.0",
|
|
49
|
+
"miniflare": "^3.20241106.2",
|
|
50
|
+
"rimraf": "^5.0.0",
|
|
51
|
+
"tsup": "^8.0.0",
|
|
52
|
+
"typescript": "^5.0.0",
|
|
53
|
+
"vitest": "^1.0.0",
|
|
54
|
+
"wrangler": "^3.0.0",
|
|
55
|
+
"@computesdk/test-utils": "1.3.1"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"clean": "rimraf dist",
|
|
60
|
+
"dev": "tsup --watch",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest watch",
|
|
63
|
+
"test:coverage": "vitest run --coverage",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"lint": "eslint"
|
|
66
|
+
}
|
|
67
|
+
}
|