@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
@@ -19,13 +19,13 @@ describe('GitClient', () => {
19
19
 
20
20
  beforeEach(() => {
21
21
  vi.clearAllMocks();
22
-
22
+
23
23
  mockFetch = vi.fn();
24
24
  global.fetch = mockFetch as unknown as typeof fetch;
25
-
25
+
26
26
  client = new GitClient({
27
27
  baseUrl: 'http://test.com',
28
- port: 3000,
28
+ port: 3000
29
29
  });
30
30
  });
31
31
 
@@ -37,18 +37,24 @@ describe('GitClient', () => {
37
37
  it('should clone public repositories successfully', async () => {
38
38
  const mockResponse: GitCheckoutResponse = {
39
39
  success: true,
40
- stdout: 'Cloning into \'react\'...\nReceiving objects: 100% (1284/1284), done.',
40
+ stdout:
41
+ "Cloning into 'react'...\nReceiving objects: 100% (1284/1284), done.",
41
42
  stderr: '',
42
43
  exitCode: 0,
43
44
  repoUrl: 'https://github.com/facebook/react.git',
44
45
  branch: 'main',
45
46
  targetDir: 'react',
46
- timestamp: '2023-01-01T00:00:00Z',
47
+ timestamp: '2023-01-01T00:00:00Z'
47
48
  };
48
49
 
49
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
50
+ mockFetch.mockResolvedValue(
51
+ new Response(JSON.stringify(mockResponse), { status: 200 })
52
+ );
50
53
 
51
- const result = await client.checkout('https://github.com/facebook/react.git', 'test-session');
54
+ const result = await client.checkout(
55
+ 'https://github.com/facebook/react.git',
56
+ 'test-session'
57
+ );
52
58
 
53
59
  expect(result.success).toBe(true);
54
60
  expect(result.repoUrl).toBe('https://github.com/facebook/react.git');
@@ -59,16 +65,18 @@ describe('GitClient', () => {
59
65
  it('should clone repositories to specific branches', async () => {
60
66
  const mockResponse: GitCheckoutResponse = {
61
67
  success: true,
62
- stdout: 'Cloning into \'project\'...\nSwitching to branch \'development\'',
68
+ stdout: "Cloning into 'project'...\nSwitching to branch 'development'",
63
69
  stderr: '',
64
70
  exitCode: 0,
65
71
  repoUrl: 'https://github.com/company/project.git',
66
72
  branch: 'development',
67
73
  targetDir: 'project',
68
- timestamp: '2023-01-01T00:00:00Z',
74
+ timestamp: '2023-01-01T00:00:00Z'
69
75
  };
70
76
 
71
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
77
+ mockFetch.mockResolvedValue(
78
+ new Response(JSON.stringify(mockResponse), { status: 200 })
79
+ );
72
80
 
73
81
  const result = await client.checkout(
74
82
  'https://github.com/company/project.git',
@@ -83,16 +91,18 @@ describe('GitClient', () => {
83
91
  it('should clone repositories to custom directories', async () => {
84
92
  const mockResponse: GitCheckoutResponse = {
85
93
  success: true,
86
- stdout: 'Cloning into \'workspace/my-app\'...\nDone.',
94
+ stdout: "Cloning into 'workspace/my-app'...\nDone.",
87
95
  stderr: '',
88
96
  exitCode: 0,
89
97
  repoUrl: 'https://github.com/user/my-app.git',
90
98
  branch: 'main',
91
99
  targetDir: 'workspace/my-app',
92
- timestamp: '2023-01-01T00:00:00Z',
100
+ timestamp: '2023-01-01T00:00:00Z'
93
101
  };
94
102
 
95
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
103
+ mockFetch.mockResolvedValue(
104
+ new Response(JSON.stringify(mockResponse), { status: 200 })
105
+ );
96
106
 
97
107
  const result = await client.checkout(
98
108
  'https://github.com/user/my-app.git',
@@ -107,18 +117,24 @@ describe('GitClient', () => {
107
117
  it('should handle large repository clones with warnings', async () => {
108
118
  const mockResponse: GitCheckoutResponse = {
109
119
  success: true,
110
- stdout: 'Cloning into \'linux\'...\nReceiving objects: 100% (8125432/8125432), 2.34 GiB, done.',
120
+ stdout:
121
+ "Cloning into 'linux'...\nReceiving objects: 100% (8125432/8125432), 2.34 GiB, done.",
111
122
  stderr: 'warning: filtering not recognized by server',
112
123
  exitCode: 0,
113
124
  repoUrl: 'https://github.com/torvalds/linux.git',
114
125
  branch: 'master',
115
126
  targetDir: 'linux',
116
- timestamp: '2023-01-01T00:05:30Z',
127
+ timestamp: '2023-01-01T00:05:30Z'
117
128
  };
118
129
 
119
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
130
+ mockFetch.mockResolvedValue(
131
+ new Response(JSON.stringify(mockResponse), { status: 200 })
132
+ );
120
133
 
121
- const result = await client.checkout('https://github.com/torvalds/linux.git', 'test-session');
134
+ const result = await client.checkout(
135
+ 'https://github.com/torvalds/linux.git',
136
+ 'test-session'
137
+ );
122
138
 
123
139
  expect(result.success).toBe(true);
124
140
  expect(result.stderr).toContain('warning:');
@@ -127,18 +143,23 @@ describe('GitClient', () => {
127
143
  it('should handle SSH repository URLs', async () => {
128
144
  const mockResponse: GitCheckoutResponse = {
129
145
  success: true,
130
- stdout: 'Cloning into \'private-project\'...\nDone.',
146
+ stdout: "Cloning into 'private-project'...\nDone.",
131
147
  stderr: '',
132
148
  exitCode: 0,
133
149
  repoUrl: 'git@github.com:company/private-project.git',
134
150
  branch: 'main',
135
151
  targetDir: 'private-project',
136
- timestamp: '2023-01-01T00:00:00Z',
152
+ timestamp: '2023-01-01T00:00:00Z'
137
153
  };
138
154
 
139
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
155
+ mockFetch.mockResolvedValue(
156
+ new Response(JSON.stringify(mockResponse), { status: 200 })
157
+ );
140
158
 
141
- const result = await client.checkout('git@github.com:company/private-project.git', 'test-session');
159
+ const result = await client.checkout(
160
+ 'git@github.com:company/private-project.git',
161
+ 'test-session'
162
+ );
142
163
 
143
164
  expect(result.success).toBe(true);
144
165
  expect(result.repoUrl).toBe('git@github.com:company/private-project.git');
@@ -149,26 +170,32 @@ describe('GitClient', () => {
149
170
  const body = JSON.parse(options.body as string);
150
171
  const repoName = body.repoUrl.split('/').pop().replace('.git', '');
151
172
 
152
- return Promise.resolve(new Response(JSON.stringify({
153
- success: true,
154
- stdout: `Cloning into '${repoName}'...\nDone.`,
155
- stderr: '',
156
- exitCode: 0,
157
- repoUrl: body.repoUrl,
158
- branch: body.branch || 'main',
159
- targetDir: body.targetDir || repoName,
160
- timestamp: new Date().toISOString(),
161
- })));
173
+ return Promise.resolve(
174
+ new Response(
175
+ JSON.stringify({
176
+ success: true,
177
+ stdout: `Cloning into '${repoName}'...\nDone.`,
178
+ stderr: '',
179
+ exitCode: 0,
180
+ repoUrl: body.repoUrl,
181
+ branch: body.branch || 'main',
182
+ targetDir: body.targetDir || repoName,
183
+ timestamp: new Date().toISOString()
184
+ })
185
+ )
186
+ );
162
187
  });
163
188
 
164
189
  const operations = await Promise.all([
165
190
  client.checkout('https://github.com/facebook/react.git', 'session-1'),
166
191
  client.checkout('https://github.com/microsoft/vscode.git', 'session-2'),
167
- client.checkout('https://github.com/nodejs/node.git', 'session-3', { branch: 'v18.x' }),
192
+ client.checkout('https://github.com/nodejs/node.git', 'session-3', {
193
+ branch: 'v18.x'
194
+ })
168
195
  ]);
169
196
 
170
197
  expect(operations).toHaveLength(3);
171
- operations.forEach(result => {
198
+ operations.forEach((result) => {
172
199
  expect(result.success).toBe(true);
173
200
  });
174
201
  expect(mockFetch).toHaveBeenCalledTimes(3);
@@ -177,96 +204,141 @@ describe('GitClient', () => {
177
204
 
178
205
  describe('repository error handling', () => {
179
206
  it('should handle repository not found errors', async () => {
180
- mockFetch.mockResolvedValue(new Response(
181
- JSON.stringify({ error: 'Repository not found', code: 'GIT_REPOSITORY_NOT_FOUND' }),
182
- { status: 404 }
183
- ));
207
+ mockFetch.mockResolvedValue(
208
+ new Response(
209
+ JSON.stringify({
210
+ error: 'Repository not found',
211
+ code: 'GIT_REPOSITORY_NOT_FOUND'
212
+ }),
213
+ { status: 404 }
214
+ )
215
+ );
184
216
 
185
- await expect(client.checkout('https://github.com/user/nonexistent.git', 'test-session'))
186
- .rejects.toThrow(GitRepositoryNotFoundError);
217
+ await expect(
218
+ client.checkout(
219
+ 'https://github.com/user/nonexistent.git',
220
+ 'test-session'
221
+ )
222
+ ).rejects.toThrow(GitRepositoryNotFoundError);
187
223
  });
188
224
 
189
225
  it('should handle authentication failures', async () => {
190
- mockFetch.mockResolvedValue(new Response(
191
- JSON.stringify({ error: 'Authentication failed', code: 'GIT_AUTH_FAILED' }),
192
- { status: 401 }
193
- ));
226
+ mockFetch.mockResolvedValue(
227
+ new Response(
228
+ JSON.stringify({
229
+ error: 'Authentication failed',
230
+ code: 'GIT_AUTH_FAILED'
231
+ }),
232
+ { status: 401 }
233
+ )
234
+ );
194
235
 
195
- await expect(client.checkout('https://github.com/company/private.git', 'test-session'))
196
- .rejects.toThrow(GitAuthenticationError);
236
+ await expect(
237
+ client.checkout(
238
+ 'https://github.com/company/private.git',
239
+ 'test-session'
240
+ )
241
+ ).rejects.toThrow(GitAuthenticationError);
197
242
  });
198
243
 
199
244
  it('should handle branch not found errors', async () => {
200
- mockFetch.mockResolvedValue(new Response(
201
- JSON.stringify({ error: 'Branch not found', code: 'GIT_BRANCH_NOT_FOUND' }),
202
- { status: 404 }
203
- ));
245
+ mockFetch.mockResolvedValue(
246
+ new Response(
247
+ JSON.stringify({
248
+ error: 'Branch not found',
249
+ code: 'GIT_BRANCH_NOT_FOUND'
250
+ }),
251
+ { status: 404 }
252
+ )
253
+ );
204
254
 
205
- await expect(client.checkout(
206
- 'https://github.com/user/repo.git',
207
- 'test-session',
208
- { branch: 'nonexistent-branch' }
209
- )).rejects.toThrow(GitBranchNotFoundError);
255
+ await expect(
256
+ client.checkout('https://github.com/user/repo.git', 'test-session', {
257
+ branch: 'nonexistent-branch'
258
+ })
259
+ ).rejects.toThrow(GitBranchNotFoundError);
210
260
  });
211
261
 
212
262
  it('should handle network errors', async () => {
213
- mockFetch.mockResolvedValue(new Response(
214
- JSON.stringify({ error: 'Network error', code: 'GIT_NETWORK_ERROR' }),
215
- { status: 503 }
216
- ));
263
+ mockFetch.mockResolvedValue(
264
+ new Response(
265
+ JSON.stringify({ error: 'Network error', code: 'GIT_NETWORK_ERROR' }),
266
+ { status: 503 }
267
+ )
268
+ );
217
269
 
218
- await expect(client.checkout('https://github.com/user/repo.git', 'test-session'))
219
- .rejects.toThrow(GitNetworkError);
270
+ await expect(
271
+ client.checkout('https://github.com/user/repo.git', 'test-session')
272
+ ).rejects.toThrow(GitNetworkError);
220
273
  });
221
274
 
222
275
  it('should handle clone failures', async () => {
223
- mockFetch.mockResolvedValue(new Response(
224
- JSON.stringify({ error: 'Clone failed', code: 'GIT_CLONE_FAILED' }),
225
- { status: 507 }
226
- ));
276
+ mockFetch.mockResolvedValue(
277
+ new Response(
278
+ JSON.stringify({ error: 'Clone failed', code: 'GIT_CLONE_FAILED' }),
279
+ { status: 507 }
280
+ )
281
+ );
227
282
 
228
- await expect(client.checkout('https://github.com/large/repository.git', 'test-session'))
229
- .rejects.toThrow(GitCloneError);
283
+ await expect(
284
+ client.checkout(
285
+ 'https://github.com/large/repository.git',
286
+ 'test-session'
287
+ )
288
+ ).rejects.toThrow(GitCloneError);
230
289
  });
231
290
 
232
291
  it('should handle checkout failures', async () => {
233
- mockFetch.mockResolvedValue(new Response(
234
- JSON.stringify({ error: 'Checkout failed', code: 'GIT_CHECKOUT_FAILED' }),
235
- { status: 409 }
236
- ));
292
+ mockFetch.mockResolvedValue(
293
+ new Response(
294
+ JSON.stringify({
295
+ error: 'Checkout failed',
296
+ code: 'GIT_CHECKOUT_FAILED'
297
+ }),
298
+ { status: 409 }
299
+ )
300
+ );
237
301
 
238
- await expect(client.checkout(
239
- 'https://github.com/user/repo.git',
240
- 'test-session',
241
- { branch: 'feature-branch' }
242
- )).rejects.toThrow(GitCheckoutError);
302
+ await expect(
303
+ client.checkout('https://github.com/user/repo.git', 'test-session', {
304
+ branch: 'feature-branch'
305
+ })
306
+ ).rejects.toThrow(GitCheckoutError);
243
307
  });
244
308
 
245
309
  it('should handle invalid Git URLs', async () => {
246
- mockFetch.mockResolvedValue(new Response(
247
- JSON.stringify({ error: 'Invalid Git URL', code: 'INVALID_GIT_URL' }),
248
- { status: 400 }
249
- ));
310
+ mockFetch.mockResolvedValue(
311
+ new Response(
312
+ JSON.stringify({ error: 'Invalid Git URL', code: 'INVALID_GIT_URL' }),
313
+ { status: 400 }
314
+ )
315
+ );
250
316
 
251
- await expect(client.checkout('not-a-valid-url', 'test-session'))
252
- .rejects.toThrow(InvalidGitUrlError);
317
+ await expect(
318
+ client.checkout('not-a-valid-url', 'test-session')
319
+ ).rejects.toThrow(InvalidGitUrlError);
253
320
  });
254
321
 
255
322
  it('should handle partial clone failures', async () => {
256
323
  const mockResponse: GitCheckoutResponse = {
257
324
  success: false,
258
- stdout: 'Cloning into \'repo\'...\nReceiving objects: 45% (450/1000)',
325
+ stdout: "Cloning into 'repo'...\nReceiving objects: 45% (450/1000)",
259
326
  stderr: 'error: RPC failed\nfatal: early EOF',
260
327
  exitCode: 128,
261
328
  repoUrl: 'https://github.com/problematic/repo.git',
262
329
  branch: 'main',
263
330
  targetDir: 'repo',
264
- timestamp: '2023-01-01T00:01:30Z',
331
+ timestamp: '2023-01-01T00:01:30Z'
265
332
  };
266
333
 
267
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
334
+ mockFetch.mockResolvedValue(
335
+ new Response(JSON.stringify(mockResponse), { status: 200 })
336
+ );
268
337
 
269
- const result = await client.checkout('https://github.com/problematic/repo.git', 'test-session');
338
+ const result = await client.checkout(
339
+ 'https://github.com/problematic/repo.git',
340
+ 'test-session'
341
+ );
270
342
 
271
343
  expect(result.success).toBe(false);
272
344
  expect(result.exitCode).toBe(128);
@@ -278,35 +350,50 @@ describe('GitClient', () => {
278
350
  it('should handle network failures', async () => {
279
351
  mockFetch.mockRejectedValue(new Error('Network connection failed'));
280
352
 
281
- await expect(client.checkout('https://github.com/user/repo.git', 'test-session'))
282
- .rejects.toThrow('Network connection failed');
353
+ await expect(
354
+ client.checkout('https://github.com/user/repo.git', 'test-session')
355
+ ).rejects.toThrow('Network connection failed');
283
356
  });
284
357
 
285
358
  it('should handle malformed server responses', async () => {
286
- mockFetch.mockResolvedValue(new Response('invalid json {', { status: 200 }));
359
+ mockFetch.mockResolvedValue(
360
+ new Response('invalid json {', { status: 200 })
361
+ );
287
362
 
288
- await expect(client.checkout('https://github.com/user/repo.git', 'test-session'))
289
- .rejects.toThrow(SandboxError);
363
+ await expect(
364
+ client.checkout('https://github.com/user/repo.git', 'test-session')
365
+ ).rejects.toThrow(SandboxError);
290
366
  });
291
367
 
292
368
  it('should map server errors to client errors', async () => {
293
369
  const serverErrorScenarios = [
294
370
  { status: 400, code: 'INVALID_GIT_URL', error: InvalidGitUrlError },
295
371
  { status: 401, code: 'GIT_AUTH_FAILED', error: GitAuthenticationError },
296
- { status: 404, code: 'GIT_REPOSITORY_NOT_FOUND', error: GitRepositoryNotFoundError },
297
- { status: 404, code: 'GIT_BRANCH_NOT_FOUND', error: GitBranchNotFoundError },
372
+ {
373
+ status: 404,
374
+ code: 'GIT_REPOSITORY_NOT_FOUND',
375
+ error: GitRepositoryNotFoundError
376
+ },
377
+ {
378
+ status: 404,
379
+ code: 'GIT_BRANCH_NOT_FOUND',
380
+ error: GitBranchNotFoundError
381
+ },
298
382
  { status: 500, code: 'GIT_OPERATION_FAILED', error: GitError },
299
- { status: 503, code: 'GIT_NETWORK_ERROR', error: GitNetworkError },
383
+ { status: 503, code: 'GIT_NETWORK_ERROR', error: GitNetworkError }
300
384
  ];
301
385
 
302
386
  for (const scenario of serverErrorScenarios) {
303
- mockFetch.mockResolvedValueOnce(new Response(
304
- JSON.stringify({ error: 'Test error', code: scenario.code }),
305
- { status: scenario.status }
306
- ));
307
-
308
- await expect(client.checkout('https://github.com/test/repo.git', 'test-session'))
309
- .rejects.toThrow(scenario.error);
387
+ mockFetch.mockResolvedValueOnce(
388
+ new Response(
389
+ JSON.stringify({ error: 'Test error', code: scenario.code }),
390
+ { status: scenario.status }
391
+ )
392
+ );
393
+
394
+ await expect(
395
+ client.checkout('https://github.com/test/repo.git', 'test-session')
396
+ ).rejects.toThrow(scenario.error);
310
397
  }
311
398
  });
312
399
  });
@@ -320,7 +407,7 @@ describe('GitClient', () => {
320
407
  it('should initialize with full options', () => {
321
408
  const fullOptionsClient = new GitClient({
322
409
  baseUrl: 'http://custom.com',
323
- port: 8080,
410
+ port: 8080
324
411
  });
325
412
  expect(fullOptionsClient).toBeInstanceOf(GitClient);
326
413
  });