@cloudflare/sandbox 0.4.12 → 0.4.15

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 +46 -16
  3. package/Dockerfile +78 -31
  4. package/README.md +9 -2
  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
@@ -1,7 +1,11 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import type { ExecuteResponse } from '../src/clients';
3
3
  import { CommandClient } from '../src/clients/command-client';
4
- import { CommandError, CommandNotFoundError, SandboxError } from '../src/errors';
4
+ import {
5
+ CommandError,
6
+ CommandNotFoundError,
7
+ SandboxError
8
+ } from '../src/errors';
5
9
 
6
10
  describe('CommandClient', () => {
7
11
  let client: CommandClient;
@@ -11,18 +15,18 @@ describe('CommandClient', () => {
11
15
 
12
16
  beforeEach(() => {
13
17
  vi.clearAllMocks();
14
-
18
+
15
19
  mockFetch = vi.fn();
16
20
  global.fetch = mockFetch as unknown as typeof fetch;
17
-
21
+
18
22
  onCommandComplete = vi.fn();
19
23
  onError = vi.fn();
20
-
24
+
21
25
  client = new CommandClient({
22
26
  baseUrl: 'http://test.com',
23
27
  port: 3000,
24
28
  onCommandComplete,
25
- onError,
29
+ onError
26
30
  });
27
31
  });
28
32
 
@@ -38,13 +42,12 @@ describe('CommandClient', () => {
38
42
  stderr: '',
39
43
  exitCode: 0,
40
44
  command: 'echo "Hello World"',
41
- timestamp: '2023-01-01T00:00:00Z',
45
+ timestamp: '2023-01-01T00:00:00Z'
42
46
  };
43
47
 
44
- mockFetch.mockResolvedValue(new Response(
45
- JSON.stringify(mockResponse),
46
- { status: 200 }
47
- ));
48
+ mockFetch.mockResolvedValue(
49
+ new Response(JSON.stringify(mockResponse), { status: 200 })
50
+ );
48
51
 
49
52
  const result = await client.execute('echo "Hello World"', 'session-exec');
50
53
 
@@ -54,7 +57,11 @@ describe('CommandClient', () => {
54
57
  expect(result.exitCode).toBe(0);
55
58
  expect(result.command).toBe('echo "Hello World"');
56
59
  expect(onCommandComplete).toHaveBeenCalledWith(
57
- true, 0, 'Hello World\n', '', 'echo "Hello World"'
60
+ true,
61
+ 0,
62
+ 'Hello World\n',
63
+ '',
64
+ 'echo "Hello World"'
58
65
  );
59
66
  });
60
67
 
@@ -65,13 +72,12 @@ describe('CommandClient', () => {
65
72
  stderr: 'command not found: nonexistent-cmd\n',
66
73
  exitCode: 127,
67
74
  command: 'nonexistent-cmd',
68
- timestamp: '2023-01-01T00:00:00Z',
75
+ timestamp: '2023-01-01T00:00:00Z'
69
76
  };
70
77
 
71
- mockFetch.mockResolvedValue(new Response(
72
- JSON.stringify(mockResponse),
73
- { status: 200 }
74
- ));
78
+ mockFetch.mockResolvedValue(
79
+ new Response(JSON.stringify(mockResponse), { status: 200 })
80
+ );
75
81
 
76
82
  const result = await client.execute('nonexistent-cmd', 'session-exec');
77
83
 
@@ -80,7 +86,11 @@ describe('CommandClient', () => {
80
86
  expect(result.stderr).toContain('command not found');
81
87
  expect(result.stdout).toBe('');
82
88
  expect(onCommandComplete).toHaveBeenCalledWith(
83
- false, 127, '', 'command not found: nonexistent-cmd\n', 'nonexistent-cmd'
89
+ false,
90
+ 127,
91
+ '',
92
+ 'command not found: nonexistent-cmd\n',
93
+ 'nonexistent-cmd'
84
94
  );
85
95
  });
86
96
 
@@ -93,13 +103,13 @@ describe('CommandClient', () => {
93
103
  timestamp: new Date().toISOString()
94
104
  };
95
105
 
96
- mockFetch.mockResolvedValue(new Response(
97
- JSON.stringify(errorResponse),
98
- { status: 404 }
99
- ));
106
+ mockFetch.mockResolvedValue(
107
+ new Response(JSON.stringify(errorResponse), { status: 404 })
108
+ );
100
109
 
101
- await expect(client.execute('invalidcmd', 'session-err'))
102
- .rejects.toThrow(CommandNotFoundError);
110
+ await expect(client.execute('invalidcmd', 'session-err')).rejects.toThrow(
111
+ CommandNotFoundError
112
+ );
103
113
  expect(onError).toHaveBeenCalledWith(
104
114
  expect.stringContaining('Command not found'),
105
115
  'invalidcmd'
@@ -109,30 +119,34 @@ describe('CommandClient', () => {
109
119
  it('should handle network failures gracefully', async () => {
110
120
  mockFetch.mockRejectedValue(new Error('Network connection failed'));
111
121
 
112
- await expect(client.execute('ls', 'session-err'))
113
- .rejects.toThrow('Network connection failed');
122
+ await expect(client.execute('ls', 'session-err')).rejects.toThrow(
123
+ 'Network connection failed'
124
+ );
114
125
  expect(onError).toHaveBeenCalledWith('Network connection failed', 'ls');
115
126
  });
116
127
 
117
128
  it('should handle server errors with proper status codes', async () => {
118
129
  const scenarios = [
119
130
  { status: 400, code: 'COMMAND_EXECUTION_ERROR', error: CommandError },
120
- { status: 500, code: 'EXECUTION_ERROR', error: SandboxError },
131
+ { status: 500, code: 'EXECUTION_ERROR', error: SandboxError }
121
132
  ];
122
133
 
123
134
  for (const scenario of scenarios) {
124
- mockFetch.mockResolvedValueOnce(new Response(
125
- JSON.stringify({
126
- code: scenario.code,
127
- message: 'Test error',
128
- context: {},
129
- httpStatus: scenario.status,
130
- timestamp: new Date().toISOString()
131
- }),
132
- { status: scenario.status }
133
- ));
134
- await expect(client.execute('test-command', 'session-err'))
135
- .rejects.toThrow(scenario.error);
135
+ mockFetch.mockResolvedValueOnce(
136
+ new Response(
137
+ JSON.stringify({
138
+ code: scenario.code,
139
+ message: 'Test error',
140
+ context: {},
141
+ httpStatus: scenario.status,
142
+ timestamp: new Date().toISOString()
143
+ }),
144
+ { status: scenario.status }
145
+ )
146
+ );
147
+ await expect(
148
+ client.execute('test-command', 'session-err')
149
+ ).rejects.toThrow(scenario.error);
136
150
  }
137
151
  });
138
152
 
@@ -144,13 +158,12 @@ describe('CommandClient', () => {
144
158
  stderr: '',
145
159
  exitCode: 0,
146
160
  command: 'find / -type f',
147
- timestamp: '2023-01-01T00:00:00Z',
161
+ timestamp: '2023-01-01T00:00:00Z'
148
162
  };
149
163
 
150
- mockFetch.mockResolvedValue(new Response(
151
- JSON.stringify(mockResponse),
152
- { status: 200 }
153
- ));
164
+ mockFetch.mockResolvedValue(
165
+ new Response(JSON.stringify(mockResponse), { status: 200 })
166
+ );
154
167
 
155
168
  const result = await client.execute('find / -type f', 'session-exec');
156
169
 
@@ -164,22 +177,24 @@ describe('CommandClient', () => {
164
177
  mockFetch.mockImplementation((url: string, options: RequestInit) => {
165
178
  const body = JSON.parse(options.body as string);
166
179
  const command = body.command;
167
- return Promise.resolve(new Response(
168
- JSON.stringify({
169
- success: true,
170
- stdout: `output for ${command}\n`,
171
- stderr: '',
172
- exitCode: 0,
173
- command: command,
174
- timestamp: '2023-01-01T00:00:00Z',
175
- }),
176
- { status: 200 }
177
- ));
180
+ return Promise.resolve(
181
+ new Response(
182
+ JSON.stringify({
183
+ success: true,
184
+ stdout: `output for ${command}\n`,
185
+ stderr: '',
186
+ exitCode: 0,
187
+ command: command,
188
+ timestamp: '2023-01-01T00:00:00Z'
189
+ }),
190
+ { status: 200 }
191
+ )
192
+ );
178
193
  });
179
194
 
180
195
  const commands = ['echo 1', 'echo 2', 'echo 3', 'pwd', 'ls'];
181
196
  const results = await Promise.all(
182
- commands.map(cmd => client.execute(cmd, 'session-concurrent'))
197
+ commands.map((cmd) => client.execute(cmd, 'session-concurrent'))
183
198
  );
184
199
 
185
200
  expect(results).toHaveLength(5);
@@ -192,13 +207,16 @@ describe('CommandClient', () => {
192
207
  });
193
208
 
194
209
  it('should handle malformed server responses', async () => {
195
- mockFetch.mockResolvedValue(new Response(
196
- 'invalid json {',
197
- { status: 200, headers: { 'Content-Type': 'application/json' } }
198
- ));
210
+ mockFetch.mockResolvedValue(
211
+ new Response('invalid json {', {
212
+ status: 200,
213
+ headers: { 'Content-Type': 'application/json' }
214
+ })
215
+ );
199
216
 
200
- await expect(client.execute('ls', 'session-err'))
201
- .rejects.toThrow(SandboxError);
217
+ await expect(client.execute('ls', 'session-err')).rejects.toThrow(
218
+ SandboxError
219
+ );
202
220
  expect(onError).toHaveBeenCalled();
203
221
  });
204
222
 
@@ -211,13 +229,13 @@ describe('CommandClient', () => {
211
229
  timestamp: new Date().toISOString()
212
230
  };
213
231
 
214
- mockFetch.mockResolvedValue(new Response(
215
- JSON.stringify(errorResponse),
216
- { status: 400 }
217
- ));
232
+ mockFetch.mockResolvedValue(
233
+ new Response(JSON.stringify(errorResponse), { status: 400 })
234
+ );
218
235
 
219
- await expect(client.execute('', 'session-err'))
220
- .rejects.toThrow(CommandError);
236
+ await expect(client.execute('', 'session-err')).rejects.toThrow(
237
+ CommandError
238
+ );
221
239
  });
222
240
 
223
241
  it('should handle streaming command execution', async () => {
@@ -235,12 +253,17 @@ describe('CommandClient', () => {
235
253
  }
236
254
  });
237
255
 
238
- mockFetch.mockResolvedValue(new Response(mockStream, {
239
- status: 200,
240
- headers: { 'Content-Type': 'text/event-stream' }
241
- }));
256
+ mockFetch.mockResolvedValue(
257
+ new Response(mockStream, {
258
+ status: 200,
259
+ headers: { 'Content-Type': 'text/event-stream' }
260
+ })
261
+ );
242
262
 
243
- const stream = await client.executeStream('tail -f app.log', 'session-stream');
263
+ const stream = await client.executeStream(
264
+ 'tail -f app.log',
265
+ 'session-stream'
266
+ );
244
267
  expect(stream).toBeInstanceOf(ReadableStream);
245
268
 
246
269
  const reader = stream.getReader();
@@ -263,7 +286,6 @@ describe('CommandClient', () => {
263
286
  expect(content).toContain('"type":"complete"');
264
287
  });
265
288
 
266
-
267
289
  it('should handle streaming errors gracefully', async () => {
268
290
  const errorResponse = {
269
291
  code: 'STREAM_START_ERROR',
@@ -273,13 +295,13 @@ describe('CommandClient', () => {
273
295
  timestamp: new Date().toISOString()
274
296
  };
275
297
 
276
- mockFetch.mockResolvedValue(new Response(
277
- JSON.stringify(errorResponse),
278
- { status: 400 }
279
- ));
298
+ mockFetch.mockResolvedValue(
299
+ new Response(JSON.stringify(errorResponse), { status: 400 })
300
+ );
280
301
 
281
- await expect(client.executeStream('invalid-stream-command', 'session-err'))
282
- .rejects.toThrow(CommandError);
302
+ await expect(
303
+ client.executeStream('invalid-stream-command', 'session-err')
304
+ ).rejects.toThrow(CommandError);
283
305
  expect(onError).toHaveBeenCalledWith(
284
306
  expect.stringContaining('Command failed to start streaming'),
285
307
  'invalid-stream-command'
@@ -287,20 +309,26 @@ describe('CommandClient', () => {
287
309
  });
288
310
 
289
311
  it('should handle streaming without response body', async () => {
290
- mockFetch.mockResolvedValue(new Response(null, {
291
- status: 200,
292
- headers: { 'Content-Type': 'text/event-stream' }
293
- }));
312
+ mockFetch.mockResolvedValue(
313
+ new Response(null, {
314
+ status: 200,
315
+ headers: { 'Content-Type': 'text/event-stream' }
316
+ })
317
+ );
294
318
 
295
- await expect(client.executeStream('test-command', 'session-err'))
296
- .rejects.toThrow('No response body for streaming');
319
+ await expect(
320
+ client.executeStream('test-command', 'session-err')
321
+ ).rejects.toThrow('No response body for streaming');
297
322
  });
298
323
 
299
324
  it('should handle network failures during streaming setup', async () => {
300
- mockFetch.mockRejectedValue(new Error('Connection lost during streaming'));
325
+ mockFetch.mockRejectedValue(
326
+ new Error('Connection lost during streaming')
327
+ );
301
328
 
302
- await expect(client.executeStream('stream-command', 'session-err'))
303
- .rejects.toThrow('Connection lost during streaming');
329
+ await expect(
330
+ client.executeStream('stream-command', 'session-err')
331
+ ).rejects.toThrow('Connection lost during streaming');
304
332
  expect(onError).toHaveBeenCalledWith(
305
333
  'Connection lost during streaming',
306
334
  'stream-command'
@@ -312,7 +340,7 @@ describe('CommandClient', () => {
312
340
  it('should work without any callbacks', async () => {
313
341
  const clientWithoutCallbacks = new CommandClient({
314
342
  baseUrl: 'http://test.com',
315
- port: 3000,
343
+ port: 3000
316
344
  });
317
345
 
318
346
  const mockResponse: ExecuteResponse = {
@@ -321,15 +349,17 @@ describe('CommandClient', () => {
321
349
  stderr: '',
322
350
  exitCode: 0,
323
351
  command: 'echo test',
324
- timestamp: '2023-01-01T00:00:00Z',
352
+ timestamp: '2023-01-01T00:00:00Z'
325
353
  };
326
354
 
327
- mockFetch.mockResolvedValue(new Response(
328
- JSON.stringify(mockResponse),
329
- { status: 200 }
330
- ));
355
+ mockFetch.mockResolvedValue(
356
+ new Response(JSON.stringify(mockResponse), { status: 200 })
357
+ );
331
358
 
332
- const result = await clientWithoutCallbacks.execute('echo test', 'session-nocb');
359
+ const result = await clientWithoutCallbacks.execute(
360
+ 'echo test',
361
+ 'session-nocb'
362
+ );
333
363
 
334
364
  expect(result.success).toBe(true);
335
365
  expect(result.stdout).toBe('test output\n');
@@ -338,13 +368,14 @@ describe('CommandClient', () => {
338
368
  it('should handle errors gracefully without callbacks', async () => {
339
369
  const clientWithoutCallbacks = new CommandClient({
340
370
  baseUrl: 'http://test.com',
341
- port: 3000,
371
+ port: 3000
342
372
  });
343
373
 
344
374
  mockFetch.mockRejectedValue(new Error('Network failed'));
345
375
 
346
- await expect(clientWithoutCallbacks.execute('test', 'session-nocb'))
347
- .rejects.toThrow('Network failed');
376
+ await expect(
377
+ clientWithoutCallbacks.execute('test', 'session-nocb')
378
+ ).rejects.toThrow('Network failed');
348
379
  });
349
380
 
350
381
  it('should call onCommandComplete for both success and failure', async () => {
@@ -354,17 +385,20 @@ describe('CommandClient', () => {
354
385
  stderr: '',
355
386
  exitCode: 0,
356
387
  command: 'echo success',
357
- timestamp: '2023-01-01T00:00:00Z',
388
+ timestamp: '2023-01-01T00:00:00Z'
358
389
  };
359
390
 
360
- mockFetch.mockResolvedValueOnce(new Response(
361
- JSON.stringify(successResponse),
362
- { status: 200 }
363
- ));
391
+ mockFetch.mockResolvedValueOnce(
392
+ new Response(JSON.stringify(successResponse), { status: 200 })
393
+ );
364
394
 
365
395
  await client.execute('echo success', 'session-cb');
366
396
  expect(onCommandComplete).toHaveBeenLastCalledWith(
367
- true, 0, 'success\n', '', 'echo success'
397
+ true,
398
+ 0,
399
+ 'success\n',
400
+ '',
401
+ 'echo success'
368
402
  );
369
403
 
370
404
  const failureResponse: ExecuteResponse = {
@@ -373,17 +407,20 @@ describe('CommandClient', () => {
373
407
  stderr: 'error\n',
374
408
  exitCode: 1,
375
409
  command: 'false',
376
- timestamp: '2023-01-01T00:00:00Z',
410
+ timestamp: '2023-01-01T00:00:00Z'
377
411
  };
378
412
 
379
- mockFetch.mockResolvedValueOnce(new Response(
380
- JSON.stringify(failureResponse),
381
- { status: 200 }
382
- ));
413
+ mockFetch.mockResolvedValueOnce(
414
+ new Response(JSON.stringify(failureResponse), { status: 200 })
415
+ );
383
416
 
384
417
  await client.execute('false', 'session-cb');
385
418
  expect(onCommandComplete).toHaveBeenLastCalledWith(
386
- false, 1, '', 'error\n', 'false'
419
+ false,
420
+ 1,
421
+ '',
422
+ 'error\n',
423
+ 'false'
387
424
  );
388
425
  });
389
426
  });
@@ -399,9 +436,9 @@ describe('CommandClient', () => {
399
436
  baseUrl: 'http://custom.com',
400
437
  port: 8080,
401
438
  onCommandComplete: vi.fn(),
402
- onError: vi.fn(),
439
+ onError: vi.fn()
403
440
  });
404
441
  expect(fullOptionsClient).toBeDefined();
405
442
  });
406
443
  });
407
- });
444
+ });