@cloudflare/sandbox 0.4.12 → 0.4.14

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.
Files changed (79) hide show
  1. package/.turbo/turbo-build.log +13 -47
  2. package/CHANGELOG.md +38 -16
  3. package/Dockerfile +15 -9
  4. package/README.md +0 -1
  5. package/dist/index.d.ts +1889 -9
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +3144 -65
  8. package/dist/index.js.map +1 -1
  9. package/package.json +5 -5
  10. package/src/clients/base-client.ts +39 -24
  11. package/src/clients/command-client.ts +8 -8
  12. package/src/clients/file-client.ts +31 -26
  13. package/src/clients/git-client.ts +3 -4
  14. package/src/clients/index.ts +12 -16
  15. package/src/clients/interpreter-client.ts +51 -47
  16. package/src/clients/port-client.ts +10 -10
  17. package/src/clients/process-client.ts +11 -8
  18. package/src/clients/sandbox-client.ts +2 -4
  19. package/src/clients/types.ts +6 -2
  20. package/src/clients/utility-client.ts +10 -6
  21. package/src/errors/adapter.ts +90 -32
  22. package/src/errors/classes.ts +189 -64
  23. package/src/errors/index.ts +9 -5
  24. package/src/file-stream.ts +11 -6
  25. package/src/index.ts +22 -15
  26. package/src/interpreter.ts +50 -41
  27. package/src/request-handler.ts +24 -21
  28. package/src/sandbox.ts +339 -149
  29. package/src/security.ts +21 -6
  30. package/src/sse-parser.ts +4 -3
  31. package/src/version.ts +1 -1
  32. package/tests/base-client.test.ts +116 -80
  33. package/tests/command-client.test.ts +149 -112
  34. package/tests/file-client.test.ts +309 -197
  35. package/tests/file-stream.test.ts +24 -20
  36. package/tests/get-sandbox.test.ts +10 -10
  37. package/tests/git-client.test.ts +188 -101
  38. package/tests/port-client.test.ts +100 -108
  39. package/tests/process-client.test.ts +204 -179
  40. package/tests/request-handler.test.ts +117 -65
  41. package/tests/sandbox.test.ts +219 -67
  42. package/tests/sse-parser.test.ts +17 -16
  43. package/tests/utility-client.test.ts +79 -72
  44. package/tsdown.config.ts +12 -0
  45. package/vitest.config.ts +6 -6
  46. package/dist/chunk-BFVUNTP4.js +0 -104
  47. package/dist/chunk-BFVUNTP4.js.map +0 -1
  48. package/dist/chunk-EKSWCBCA.js +0 -86
  49. package/dist/chunk-EKSWCBCA.js.map +0 -1
  50. package/dist/chunk-JXZMAU2C.js +0 -559
  51. package/dist/chunk-JXZMAU2C.js.map +0 -1
  52. package/dist/chunk-UJ3TV4M6.js +0 -7
  53. package/dist/chunk-UJ3TV4M6.js.map +0 -1
  54. package/dist/chunk-YE265ASX.js +0 -2484
  55. package/dist/chunk-YE265ASX.js.map +0 -1
  56. package/dist/chunk-Z532A7QC.js +0 -78
  57. package/dist/chunk-Z532A7QC.js.map +0 -1
  58. package/dist/file-stream.d.ts +0 -43
  59. package/dist/file-stream.js +0 -9
  60. package/dist/file-stream.js.map +0 -1
  61. package/dist/interpreter.d.ts +0 -33
  62. package/dist/interpreter.js +0 -8
  63. package/dist/interpreter.js.map +0 -1
  64. package/dist/request-handler.d.ts +0 -18
  65. package/dist/request-handler.js +0 -13
  66. package/dist/request-handler.js.map +0 -1
  67. package/dist/sandbox-CLZWpfGc.d.ts +0 -613
  68. package/dist/sandbox.d.ts +0 -4
  69. package/dist/sandbox.js +0 -13
  70. package/dist/sandbox.js.map +0 -1
  71. package/dist/security.d.ts +0 -31
  72. package/dist/security.js +0 -13
  73. package/dist/security.js.map +0 -1
  74. package/dist/sse-parser.d.ts +0 -28
  75. package/dist/sse-parser.js +0 -11
  76. package/dist/sse-parser.js.map +0 -1
  77. package/dist/version.d.ts +0 -8
  78. package/dist/version.js +0 -7
  79. package/dist/version.js.map +0 -1
@@ -6,11 +6,11 @@ import {
6
6
  type OutputMessage,
7
7
  type Result,
8
8
  ResultImpl,
9
- type RunCodeOptions,
10
- } from "@repo/shared";
11
- import type { InterpreterClient } from "./clients/interpreter-client.js";
12
- import type { Sandbox } from "./sandbox.js";
13
- import { validateLanguage } from "./security.js";
9
+ type RunCodeOptions
10
+ } from '@repo/shared';
11
+ import type { InterpreterClient } from './clients/interpreter-client.js';
12
+ import type { Sandbox } from './sandbox.js';
13
+ import { validateLanguage } from './security.js';
14
14
 
15
15
  export class CodeInterpreter {
16
16
  private interpreterClient: InterpreterClient;
@@ -18,7 +18,8 @@ export class CodeInterpreter {
18
18
 
19
19
  constructor(sandbox: Sandbox) {
20
20
  // In init-testing architecture, client is a SandboxClient with an interpreter property
21
- this.interpreterClient = (sandbox.client as any).interpreter as InterpreterClient;
21
+ this.interpreterClient = (sandbox.client as any)
22
+ .interpreter as InterpreterClient;
22
23
  }
23
24
 
24
25
  /**
@@ -46,7 +47,7 @@ export class CodeInterpreter {
46
47
  let context = options.context;
47
48
  if (!context) {
48
49
  // Try to find or create a default context for the language
49
- const language = options.language || "python";
50
+ const language = options.language || 'python';
50
51
  context = await this.getOrCreateDefaultContext(language);
51
52
  }
52
53
 
@@ -54,24 +55,29 @@ export class CodeInterpreter {
54
55
  const execution = new Execution(code, context);
55
56
 
56
57
  // Stream execution
57
- await this.interpreterClient.runCodeStream(context.id, code, options.language, {
58
- onStdout: (output: OutputMessage) => {
59
- execution.logs.stdout.push(output.text);
60
- if (options.onStdout) return options.onStdout(output);
61
- },
62
- onStderr: (output: OutputMessage) => {
63
- execution.logs.stderr.push(output.text);
64
- if (options.onStderr) return options.onStderr(output);
65
- },
66
- onResult: async (result: Result) => {
67
- execution.results.push(new ResultImpl(result) as any);
68
- if (options.onResult) return options.onResult(result);
69
- },
70
- onError: (error: ExecutionError) => {
71
- execution.error = error;
72
- if (options.onError) return options.onError(error);
73
- },
74
- });
58
+ await this.interpreterClient.runCodeStream(
59
+ context.id,
60
+ code,
61
+ options.language,
62
+ {
63
+ onStdout: (output: OutputMessage) => {
64
+ execution.logs.stdout.push(output.text);
65
+ if (options.onStdout) return options.onStdout(output);
66
+ },
67
+ onStderr: (output: OutputMessage) => {
68
+ execution.logs.stderr.push(output.text);
69
+ if (options.onStderr) return options.onStderr(output);
70
+ },
71
+ onResult: async (result: Result) => {
72
+ execution.results.push(new ResultImpl(result) as any);
73
+ if (options.onResult) return options.onResult(result);
74
+ },
75
+ onError: (error: ExecutionError) => {
76
+ execution.error = error;
77
+ if (options.onError) return options.onError(error);
78
+ }
79
+ }
80
+ );
75
81
 
76
82
  return execution;
77
83
  }
@@ -86,36 +92,39 @@ export class CodeInterpreter {
86
92
  // Get or create context
87
93
  let context = options.context;
88
94
  if (!context) {
89
- const language = options.language || "python";
95
+ const language = options.language || 'python';
90
96
  context = await this.getOrCreateDefaultContext(language);
91
97
  }
92
98
 
93
99
  // Create streaming response
94
100
  // Note: doFetch is protected but we need direct access for raw stream response
95
- const response = await (this.interpreterClient as any).doFetch("/api/execute/code", {
96
- method: "POST",
97
- headers: {
98
- "Content-Type": "application/json",
99
- Accept: "text/event-stream",
100
- },
101
- body: JSON.stringify({
102
- context_id: context.id,
103
- code,
104
- language: options.language,
105
- }),
106
- });
101
+ const response = await (this.interpreterClient as any).doFetch(
102
+ '/api/execute/code',
103
+ {
104
+ method: 'POST',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ Accept: 'text/event-stream'
108
+ },
109
+ body: JSON.stringify({
110
+ context_id: context.id,
111
+ code,
112
+ language: options.language
113
+ })
114
+ }
115
+ );
107
116
 
108
117
  if (!response.ok) {
109
118
  const errorData = (await response
110
119
  .json()
111
- .catch(() => ({ error: "Unknown error" }))) as { error?: string };
120
+ .catch(() => ({ error: 'Unknown error' }))) as { error?: string };
112
121
  throw new Error(
113
122
  errorData.error || `Failed to execute code: ${response.status}`
114
123
  );
115
124
  }
116
125
 
117
126
  if (!response.body) {
118
- throw new Error("No response body for streaming execution");
127
+ throw new Error('No response body for streaming execution');
119
128
  }
120
129
 
121
130
  return response.body;
@@ -144,7 +153,7 @@ export class CodeInterpreter {
144
153
  }
145
154
 
146
155
  private async getOrCreateDefaultContext(
147
- language: "python" | "javascript" | "typescript"
156
+ language: 'python' | 'javascript' | 'typescript'
148
157
  ): Promise<CodeContext> {
149
158
  // Check if we have a cached context for this language
150
159
  for (const context of this.contexts.values()) {
@@ -1,10 +1,7 @@
1
- import { switchPort } from "@cloudflare/containers";
2
- import { createLogger, type LogContext, TraceContext } from "@repo/shared";
3
- import { getSandbox, type Sandbox } from "./sandbox";
4
- import {
5
- sanitizeSandboxId,
6
- validatePort
7
- } from "./security";
1
+ import { switchPort } from '@cloudflare/containers';
2
+ import { createLogger, type LogContext, TraceContext } from '@repo/shared';
3
+ import { getSandbox, type Sandbox } from './sandbox';
4
+ import { sanitizeSandboxId, validatePort } from './security';
8
5
 
9
6
  export interface SandboxEnv {
10
7
  Sandbox: DurableObjectNamespace<Sandbox>;
@@ -22,7 +19,8 @@ export async function proxyToSandbox<E extends SandboxEnv>(
22
19
  env: E
23
20
  ): Promise<Response | null> {
24
21
  // Create logger context for this request
25
- const traceId = TraceContext.fromHeaders(request.headers) || TraceContext.generate();
22
+ const traceId =
23
+ TraceContext.fromHeaders(request.headers) || TraceContext.generate();
26
24
  const logger = createLogger({
27
25
  component: 'sandbox-do',
28
26
  traceId,
@@ -98,16 +96,19 @@ export async function proxyToSandbox<E extends SandboxEnv>(
98
96
  'X-Original-URL': request.url,
99
97
  'X-Forwarded-Host': url.hostname,
100
98
  'X-Forwarded-Proto': url.protocol.replace(':', ''),
101
- 'X-Sandbox-Name': sandboxId, // Pass the friendly name
99
+ 'X-Sandbox-Name': sandboxId // Pass the friendly name
102
100
  },
103
101
  body: request.body,
104
102
  // @ts-expect-error - duplex required for body streaming in modern runtimes
105
- duplex: 'half',
103
+ duplex: 'half'
106
104
  });
107
105
 
108
106
  return await sandbox.containerFetch(proxyRequest, port);
109
107
  } catch (error) {
110
- logger.error('Proxy routing error', error instanceof Error ? error : new Error(String(error)));
108
+ logger.error(
109
+ 'Proxy routing error',
110
+ error instanceof Error ? error : new Error(String(error))
111
+ );
111
112
  return new Response('Proxy routing error', { status: 500 });
112
113
  }
113
114
  }
@@ -115,7 +116,9 @@ export async function proxyToSandbox<E extends SandboxEnv>(
115
116
  function extractSandboxRoute(url: URL): RouteInfo | null {
116
117
  // Parse subdomain pattern: port-sandboxId-token.domain (tokens mandatory)
117
118
  // Token is always exactly 16 chars (generated by generatePortToken)
118
- const subdomainMatch = url.hostname.match(/^(\d{4,5})-([^.-][^.]*?[^.-]|[^.-])-([a-z0-9_-]{16})\.(.+)$/);
119
+ const subdomainMatch = url.hostname.match(
120
+ /^(\d{4,5})-([^.-][^.]*?[^.-]|[^.-])-([a-z0-9_-]{16})\.(.+)$/
121
+ );
119
122
 
120
123
  if (!subdomainMatch) {
121
124
  return null;
@@ -146,8 +149,8 @@ function extractSandboxRoute(url: URL): RouteInfo | null {
146
149
  return {
147
150
  port,
148
151
  sandboxId: sanitizedSandboxId,
149
- path: url.pathname || "/",
150
- token,
152
+ path: url.pathname || '/',
153
+ token
151
154
  };
152
155
  }
153
156
 
@@ -163,18 +166,18 @@ export function isLocalhostPattern(hostname: string): boolean {
163
166
  return hostname === '[::1]';
164
167
  }
165
168
  }
166
-
169
+
167
170
  // Handle bare IPv6 without brackets
168
171
  if (hostname === '::1') {
169
172
  return true;
170
173
  }
171
-
174
+
172
175
  // For IPv4 and regular hostnames, split on colon to remove port
173
- const hostPart = hostname.split(":")[0];
174
-
176
+ const hostPart = hostname.split(':')[0];
177
+
175
178
  return (
176
- hostPart === "localhost" ||
177
- hostPart === "127.0.0.1" ||
178
- hostPart === "0.0.0.0"
179
+ hostPart === 'localhost' ||
180
+ hostPart === '127.0.0.1' ||
181
+ hostPart === '0.0.0.0'
179
182
  );
180
183
  }