@cloudflare/sandbox 0.3.7 → 0.4.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.
Files changed (120) hide show
  1. package/.turbo/turbo-build.log +44 -0
  2. package/CHANGELOG.md +6 -14
  3. package/Dockerfile +82 -18
  4. package/README.md +89 -824
  5. package/dist/{chunk-JTKON2SH.js → chunk-BCJ7SF3Q.js} +9 -5
  6. package/dist/chunk-BCJ7SF3Q.js.map +1 -0
  7. package/dist/chunk-BFVUNTP4.js +104 -0
  8. package/dist/chunk-BFVUNTP4.js.map +1 -0
  9. package/dist/{chunk-NNGBXDMY.js → chunk-EKSWCBCA.js} +3 -6
  10. package/dist/chunk-EKSWCBCA.js.map +1 -0
  11. package/dist/chunk-HGF554LH.js +2236 -0
  12. package/dist/chunk-HGF554LH.js.map +1 -0
  13. package/dist/{chunk-6UAWTJ5S.js → chunk-Z532A7QC.js} +13 -20
  14. package/dist/{chunk-6UAWTJ5S.js.map → chunk-Z532A7QC.js.map} +1 -1
  15. package/dist/file-stream.d.ts +16 -38
  16. package/dist/file-stream.js +1 -2
  17. package/dist/index.d.ts +6 -5
  18. package/dist/index.js +35 -39
  19. package/dist/index.js.map +1 -1
  20. package/dist/interpreter.d.ts +3 -3
  21. package/dist/interpreter.js +2 -2
  22. package/dist/request-handler.d.ts +4 -3
  23. package/dist/request-handler.js +4 -7
  24. package/dist/sandbox-D9K2ypln.d.ts +583 -0
  25. package/dist/sandbox.d.ts +3 -3
  26. package/dist/sandbox.js +4 -7
  27. package/dist/security.d.ts +4 -3
  28. package/dist/security.js +3 -3
  29. package/dist/sse-parser.js +1 -1
  30. package/package.json +11 -5
  31. package/src/clients/base-client.ts +280 -0
  32. package/src/clients/command-client.ts +115 -0
  33. package/src/clients/file-client.ts +269 -0
  34. package/src/clients/git-client.ts +92 -0
  35. package/src/clients/index.ts +63 -0
  36. package/src/{interpreter-client.ts → clients/interpreter-client.ts} +148 -171
  37. package/src/clients/port-client.ts +105 -0
  38. package/src/clients/process-client.ts +177 -0
  39. package/src/clients/sandbox-client.ts +41 -0
  40. package/src/clients/types.ts +84 -0
  41. package/src/clients/utility-client.ts +94 -0
  42. package/src/errors/adapter.ts +180 -0
  43. package/src/errors/classes.ts +469 -0
  44. package/src/errors/index.ts +105 -0
  45. package/src/file-stream.ts +119 -117
  46. package/src/index.ts +81 -69
  47. package/src/interpreter.ts +17 -8
  48. package/src/request-handler.ts +69 -43
  49. package/src/sandbox.ts +694 -533
  50. package/src/security.ts +14 -23
  51. package/src/sse-parser.ts +4 -8
  52. package/startup.sh +3 -0
  53. package/tests/base-client.test.ts +328 -0
  54. package/tests/command-client.test.ts +407 -0
  55. package/tests/file-client.test.ts +643 -0
  56. package/tests/file-stream.test.ts +306 -0
  57. package/tests/git-client.test.ts +328 -0
  58. package/tests/port-client.test.ts +301 -0
  59. package/tests/process-client.test.ts +658 -0
  60. package/tests/sandbox.test.ts +465 -0
  61. package/tests/sse-parser.test.ts +290 -0
  62. package/tests/utility-client.test.ts +266 -0
  63. package/tests/wrangler.jsonc +35 -0
  64. package/tsconfig.json +9 -1
  65. package/vitest.config.ts +31 -0
  66. package/container_src/bun.lock +0 -76
  67. package/container_src/circuit-breaker.ts +0 -121
  68. package/container_src/control-process.ts +0 -784
  69. package/container_src/handler/exec.ts +0 -185
  70. package/container_src/handler/file.ts +0 -457
  71. package/container_src/handler/git.ts +0 -130
  72. package/container_src/handler/ports.ts +0 -314
  73. package/container_src/handler/process.ts +0 -568
  74. package/container_src/handler/session.ts +0 -92
  75. package/container_src/index.ts +0 -601
  76. package/container_src/interpreter-service.ts +0 -276
  77. package/container_src/isolation.ts +0 -1213
  78. package/container_src/mime-processor.ts +0 -255
  79. package/container_src/package.json +0 -18
  80. package/container_src/runtime/executors/javascript/node_executor.ts +0 -123
  81. package/container_src/runtime/executors/python/ipython_executor.py +0 -338
  82. package/container_src/runtime/executors/typescript/ts_executor.ts +0 -138
  83. package/container_src/runtime/process-pool.ts +0 -464
  84. package/container_src/shell-escape.ts +0 -42
  85. package/container_src/startup.sh +0 -11
  86. package/container_src/types.ts +0 -131
  87. package/dist/chunk-32UDXUPC.js +0 -671
  88. package/dist/chunk-32UDXUPC.js.map +0 -1
  89. package/dist/chunk-5DILEXGY.js +0 -85
  90. package/dist/chunk-5DILEXGY.js.map +0 -1
  91. package/dist/chunk-D3U63BZP.js +0 -240
  92. package/dist/chunk-D3U63BZP.js.map +0 -1
  93. package/dist/chunk-FXYPFGOZ.js +0 -129
  94. package/dist/chunk-FXYPFGOZ.js.map +0 -1
  95. package/dist/chunk-JTKON2SH.js.map +0 -1
  96. package/dist/chunk-NNGBXDMY.js.map +0 -1
  97. package/dist/chunk-SQLJNZ3K.js +0 -674
  98. package/dist/chunk-SQLJNZ3K.js.map +0 -1
  99. package/dist/chunk-W7TVRPBG.js +0 -108
  100. package/dist/chunk-W7TVRPBG.js.map +0 -1
  101. package/dist/client-B3RUab0s.d.ts +0 -225
  102. package/dist/client.d.ts +0 -4
  103. package/dist/client.js +0 -7
  104. package/dist/client.js.map +0 -1
  105. package/dist/errors.d.ts +0 -95
  106. package/dist/errors.js +0 -27
  107. package/dist/errors.js.map +0 -1
  108. package/dist/interpreter-client.d.ts +0 -4
  109. package/dist/interpreter-client.js +0 -9
  110. package/dist/interpreter-client.js.map +0 -1
  111. package/dist/interpreter-types.d.ts +0 -259
  112. package/dist/interpreter-types.js +0 -9
  113. package/dist/interpreter-types.js.map +0 -1
  114. package/dist/types.d.ts +0 -453
  115. package/dist/types.js +0 -45
  116. package/dist/types.js.map +0 -1
  117. package/src/client.ts +0 -1048
  118. package/src/errors.ts +0 -219
  119. package/src/interpreter-types.ts +0 -390
  120. package/src/types.ts +0 -571
@@ -0,0 +1,301 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import type {
3
+ ExposePortResponse,
4
+ GetExposedPortsResponse,
5
+ UnexposePortResponse
6
+ } from '../src/clients';
7
+ import { PortClient } from '../src/clients/port-client';
8
+ import {
9
+ InvalidPortError,
10
+ PortAlreadyExposedError,
11
+ PortError,
12
+ PortInUseError,
13
+ PortNotExposedError,
14
+ SandboxError,
15
+ ServiceNotRespondingError
16
+ } from '../src/errors';
17
+
18
+ describe('PortClient', () => {
19
+ let client: PortClient;
20
+ let mockFetch: ReturnType<typeof vi.fn>;
21
+
22
+ beforeEach(() => {
23
+ vi.clearAllMocks();
24
+
25
+ mockFetch = vi.fn();
26
+ global.fetch = mockFetch as unknown as typeof fetch;
27
+
28
+ client = new PortClient({
29
+ baseUrl: 'http://test.com',
30
+ port: 3000,
31
+ });
32
+ });
33
+
34
+ afterEach(() => {
35
+ vi.restoreAllMocks();
36
+ });
37
+
38
+ describe('service exposure', () => {
39
+ it('should expose web services successfully', async () => {
40
+ const mockResponse: ExposePortResponse = {
41
+ success: true,
42
+ port: 3001,
43
+ exposedAt: 'https://preview-abc123.workers.dev',
44
+ name: 'web-server',
45
+ timestamp: '2023-01-01T00:00:00Z',
46
+ };
47
+
48
+ mockFetch.mockResolvedValue(new Response(
49
+ JSON.stringify(mockResponse),
50
+ { status: 200 }
51
+ ));
52
+
53
+ const result = await client.exposePort(3001, 'session-123', 'web-server');
54
+
55
+ expect(result.success).toBe(true);
56
+ expect(result.port).toBe(3001);
57
+ expect(result.exposedAt).toBe('https://preview-abc123.workers.dev');
58
+ expect(result.name).toBe('web-server');
59
+ expect(result.exposedAt.startsWith('https://')).toBe(true);
60
+ });
61
+
62
+ it('should expose API services on different ports', async () => {
63
+ const mockResponse: ExposePortResponse = {
64
+ success: true,
65
+ port: 8080,
66
+ exposedAt: 'https://api-def456.workers.dev',
67
+ name: 'api-server',
68
+ timestamp: '2023-01-01T00:00:00Z',
69
+ };
70
+
71
+ mockFetch.mockResolvedValue(new Response(
72
+ JSON.stringify(mockResponse),
73
+ { status: 200 }
74
+ ));
75
+
76
+ const result = await client.exposePort(8080, 'session-456', 'api-server');
77
+
78
+ expect(result.success).toBe(true);
79
+ expect(result.port).toBe(8080);
80
+ expect(result.name).toBe('api-server');
81
+ expect(result.exposedAt).toContain('api-');
82
+ });
83
+
84
+ it('should expose services without explicit names', async () => {
85
+ const mockResponse: ExposePortResponse = {
86
+ success: true,
87
+ port: 5000,
88
+ exposedAt: 'https://service-ghi789.workers.dev',
89
+ timestamp: '2023-01-01T00:00:00Z',
90
+ };
91
+
92
+ mockFetch.mockResolvedValue(new Response(
93
+ JSON.stringify(mockResponse),
94
+ { status: 200 }
95
+ ));
96
+
97
+ const result = await client.exposePort(5000, 'session-789');
98
+
99
+ expect(result.success).toBe(true);
100
+ expect(result.port).toBe(5000);
101
+ expect(result.name).toBeUndefined();
102
+ expect(result.exposedAt).toBeDefined();
103
+ });
104
+
105
+ });
106
+
107
+ describe('service management', () => {
108
+ it('should list all exposed services', async () => {
109
+ const mockResponse: GetExposedPortsResponse = {
110
+ success: true,
111
+ ports: [
112
+ {
113
+ port: 3000,
114
+ exposedAt: 'https://frontend-abc123.workers.dev',
115
+ name: 'frontend',
116
+ },
117
+ {
118
+ port: 4000,
119
+ exposedAt: 'https://api-def456.workers.dev',
120
+ name: 'api',
121
+ },
122
+ {
123
+ port: 5432,
124
+ exposedAt: 'https://db-ghi789.workers.dev',
125
+ name: 'database',
126
+ }
127
+ ],
128
+ count: 3,
129
+ timestamp: '2023-01-01T00:10:00Z',
130
+ };
131
+
132
+ mockFetch.mockResolvedValue(new Response(
133
+ JSON.stringify(mockResponse),
134
+ { status: 200 }
135
+ ));
136
+
137
+ const result = await client.getExposedPorts('session-list');
138
+
139
+ expect(result.success).toBe(true);
140
+ expect(result.count).toBe(3);
141
+ expect(result.ports).toHaveLength(3);
142
+
143
+ result.ports.forEach(service => {
144
+ expect(service.exposedAt).toContain('.workers.dev');
145
+ expect(service.port).toBeGreaterThan(0);
146
+ expect(service.name).toBeDefined();
147
+ });
148
+ });
149
+
150
+ it('should handle empty exposed ports list', async () => {
151
+ const mockResponse: GetExposedPortsResponse = {
152
+ success: true,
153
+ ports: [],
154
+ count: 0,
155
+ timestamp: '2023-01-01T00:00:00Z',
156
+ };
157
+
158
+ mockFetch.mockResolvedValue(new Response(
159
+ JSON.stringify(mockResponse),
160
+ { status: 200 }
161
+ ));
162
+
163
+ const result = await client.getExposedPorts('session-empty');
164
+
165
+ expect(result.success).toBe(true);
166
+ expect(result.count).toBe(0);
167
+ expect(result.ports).toHaveLength(0);
168
+ });
169
+
170
+ it('should unexpose services cleanly', async () => {
171
+ const mockResponse: UnexposePortResponse = {
172
+ success: true,
173
+ port: 3001,
174
+ timestamp: '2023-01-01T00:15:00Z',
175
+ };
176
+
177
+ mockFetch.mockResolvedValue(new Response(
178
+ JSON.stringify(mockResponse),
179
+ { status: 200 }
180
+ ));
181
+
182
+ const result = await client.unexposePort(3001, 'session-unexpose');
183
+
184
+ expect(result.success).toBe(true);
185
+ expect(result.port).toBe(3001);
186
+ });
187
+
188
+ });
189
+
190
+ describe('port validation and error handling', () => {
191
+ it('should handle port already exposed errors', async () => {
192
+ const errorResponse = {
193
+ error: 'Port already exposed: 3000',
194
+ code: 'PORT_ALREADY_EXPOSED'
195
+ };
196
+
197
+ mockFetch.mockResolvedValue(new Response(
198
+ JSON.stringify(errorResponse),
199
+ { status: 409 }
200
+ ));
201
+
202
+ await expect(client.exposePort(3000, 'session-err'))
203
+ .rejects.toThrow(PortAlreadyExposedError);
204
+ });
205
+
206
+ it('should handle invalid port numbers', async () => {
207
+ const errorResponse = {
208
+ error: 'Invalid port number: 0',
209
+ code: 'INVALID_PORT_NUMBER'
210
+ };
211
+
212
+ mockFetch.mockResolvedValue(new Response(
213
+ JSON.stringify(errorResponse),
214
+ { status: 400 }
215
+ ));
216
+
217
+ await expect(client.exposePort(0, 'session-err'))
218
+ .rejects.toThrow(InvalidPortError);
219
+ });
220
+
221
+ it('should handle port in use errors', async () => {
222
+ const errorResponse = {
223
+ error: 'Port in use: 3000 is already bound by another process',
224
+ code: 'PORT_IN_USE'
225
+ };
226
+
227
+ mockFetch.mockResolvedValue(new Response(
228
+ JSON.stringify(errorResponse),
229
+ { status: 409 }
230
+ ));
231
+
232
+ await expect(client.exposePort(3000, 'session-err'))
233
+ .rejects.toThrow(PortInUseError);
234
+ });
235
+
236
+ it('should handle service not responding errors', async () => {
237
+ const errorResponse = {
238
+ error: 'Service not responding on port 8080',
239
+ code: 'SERVICE_NOT_RESPONDING'
240
+ };
241
+
242
+ mockFetch.mockResolvedValue(new Response(
243
+ JSON.stringify(errorResponse),
244
+ { status: 503 }
245
+ ));
246
+
247
+ await expect(client.exposePort(8080, 'session-err'))
248
+ .rejects.toThrow(ServiceNotRespondingError);
249
+ });
250
+
251
+ it('should handle unexpose non-existent port', async () => {
252
+ const errorResponse = {
253
+ error: 'Port not exposed: 9999',
254
+ code: 'PORT_NOT_EXPOSED'
255
+ };
256
+
257
+ mockFetch.mockResolvedValue(new Response(
258
+ JSON.stringify(errorResponse),
259
+ { status: 404 }
260
+ ));
261
+
262
+ await expect(client.unexposePort(9999, 'session-err'))
263
+ .rejects.toThrow(PortNotExposedError);
264
+ });
265
+
266
+ it('should handle port operation failures', async () => {
267
+ const errorResponse = {
268
+ error: 'Port operation failed: unable to setup proxy',
269
+ code: 'PORT_OPERATION_ERROR'
270
+ };
271
+
272
+ mockFetch.mockResolvedValue(new Response(
273
+ JSON.stringify(errorResponse),
274
+ { status: 500 }
275
+ ));
276
+
277
+ await expect(client.exposePort(3000, 'session-err'))
278
+ .rejects.toThrow(PortError);
279
+ });
280
+ });
281
+
282
+ describe('edge cases and resilience', () => {
283
+ it('should handle network failures gracefully', async () => {
284
+ mockFetch.mockRejectedValue(new Error('Network connection failed'));
285
+
286
+ await expect(client.exposePort(3000, 'session-net'))
287
+ .rejects.toThrow('Network connection failed');
288
+ });
289
+
290
+ it('should handle malformed server responses', async () => {
291
+ mockFetch.mockResolvedValue(new Response(
292
+ 'invalid json {',
293
+ { status: 200 }
294
+ ));
295
+
296
+ await expect(client.exposePort(3000, 'session-malform'))
297
+ .rejects.toThrow(SandboxError);
298
+ });
299
+
300
+ });
301
+ });