@cloudflare/sandbox 0.0.0-6d1b288 → 0.0.0-6f16c81

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/package.json CHANGED
@@ -1,12 +1,25 @@
1
1
  {
2
2
  "name": "@cloudflare/sandbox",
3
- "version": "0.0.0-6d1b288",
3
+ "version": "0.0.0-6f16c81",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/cloudflare/sandbox-sdk"
7
+ },
4
8
  "description": "A sandboxed environment for running commands",
5
9
  "dependencies": {
6
- "@cloudflare/containers": "^0.0.8"
10
+ "@cloudflare/containers": "^0.0.23"
7
11
  },
12
+ "tags": [
13
+ "sandbox",
14
+ "codegen",
15
+ "containers",
16
+ "cloudflare",
17
+ "durable objects"
18
+ ],
8
19
  "scripts": {
9
- "build": "tsup src/*.ts --outDir dist --dts --sourcemap --format esm"
20
+ "build": "rm -rf dist && tsup src/*.ts --outDir dist --dts --sourcemap --format esm",
21
+ "docker:build": "docker build -t ghostwriternr/cloudflare-sandbox:$npm_package_version .",
22
+ "docker:publish": "docker push docker.io/ghostwriternr/cloudflare-sandbox:$npm_package_version"
10
23
  },
11
24
  "exports": {
12
25
  ".": {
package/src/client.ts CHANGED
@@ -1,9 +1,10 @@
1
- import type { DurableObject } from "cloudflare:workers";
2
1
  import type { Sandbox } from "./index";
3
2
 
4
3
  interface ExecuteRequest {
5
4
  command: string;
6
5
  args?: string[];
6
+ sessionId?: string;
7
+ background?: boolean;
7
8
  }
8
9
 
9
10
  export interface ExecuteResponse {
@@ -85,6 +86,20 @@ export interface WriteFileResponse {
85
86
  timestamp: string;
86
87
  }
87
88
 
89
+ interface ReadFileRequest {
90
+ path: string;
91
+ encoding?: string;
92
+ sessionId?: string;
93
+ }
94
+
95
+ export interface ReadFileResponse {
96
+ success: boolean;
97
+ exitCode: number;
98
+ path: string;
99
+ content: string;
100
+ timestamp: string;
101
+ }
102
+
88
103
  interface DeleteFileRequest {
89
104
  path: string;
90
105
  sessionId?: string;
@@ -125,6 +140,37 @@ export interface MoveFileResponse {
125
140
  timestamp: string;
126
141
  }
127
142
 
143
+ interface PreviewInfo {
144
+ url: string;
145
+ port: number;
146
+ name?: string;
147
+ }
148
+
149
+ interface ExposedPort extends PreviewInfo {
150
+ exposedAt: string;
151
+ timestamp: string;
152
+ }
153
+
154
+ interface ExposePortResponse {
155
+ success: boolean;
156
+ port: number;
157
+ name?: string;
158
+ exposedAt: string;
159
+ timestamp: string;
160
+ }
161
+
162
+ interface UnexposePortResponse {
163
+ success: boolean;
164
+ port: number;
165
+ timestamp: string;
166
+ }
167
+
168
+ interface GetExposedPortsResponse {
169
+ ports: ExposedPort[];
170
+ count: number;
171
+ timestamp: string;
172
+ }
173
+
128
174
  interface PingResponse {
129
175
  message: string;
130
176
  timestamp: string;
@@ -142,6 +188,7 @@ interface StreamEvent {
142
188
  newPath?: string;
143
189
  sourcePath?: string;
144
190
  destinationPath?: string;
191
+ content?: string;
145
192
  success?: boolean;
146
193
  exitCode?: number;
147
194
  stdout?: string;
@@ -188,10 +235,41 @@ export class HttpClient {
188
235
  path: string,
189
236
  options?: RequestInit
190
237
  ): Promise<Response> {
191
- if (this.options.stub) {
192
- return this.options.stub.containerFetch(path, options, this.options.port);
238
+ const url = this.options.stub
239
+ ? `http://localhost:${this.options.port}${path}`
240
+ : `${this.baseUrl}${path}`;
241
+ const method = options?.method || "GET";
242
+
243
+ console.log(`[HTTP Client] Making ${method} request to ${url}`);
244
+
245
+ try {
246
+ let response: Response;
247
+
248
+ if (this.options.stub) {
249
+ response = await this.options.stub.containerFetch(
250
+ url,
251
+ options,
252
+ this.options.port
253
+ );
254
+ } else {
255
+ response = await fetch(url, options);
256
+ }
257
+
258
+ console.log(
259
+ `[HTTP Client] Response: ${response.status} ${response.statusText}`
260
+ );
261
+
262
+ if (!response.ok) {
263
+ console.error(
264
+ `[HTTP Client] Request failed: ${method} ${url} - ${response.status} ${response.statusText}`
265
+ );
266
+ }
267
+
268
+ return response;
269
+ } catch (error) {
270
+ console.error(`[HTTP Client] Request error: ${method} ${url}`, error);
271
+ throw error;
193
272
  }
194
- return fetch(this.baseUrl + path, options);
195
273
  }
196
274
  // Public methods to set event handlers
197
275
  setOnOutput(
@@ -230,13 +308,13 @@ export class HttpClient {
230
308
 
231
309
  getOnCommandComplete():
232
310
  | ((
233
- success: boolean,
234
- exitCode: number,
235
- stdout: string,
236
- stderr: string,
237
- command: string,
238
- args: string[]
239
- ) => void)
311
+ success: boolean,
312
+ exitCode: number,
313
+ stdout: string,
314
+ stderr: string,
315
+ command: string,
316
+ args: string[]
317
+ ) => void)
240
318
  | undefined {
241
319
  return this.options.onCommandComplete;
242
320
  }
@@ -293,7 +371,8 @@ export class HttpClient {
293
371
  async execute(
294
372
  command: string,
295
373
  args: string[] = [],
296
- sessionId?: string
374
+ sessionId?: string,
375
+ background: boolean = false,
297
376
  ): Promise<ExecuteResponse> {
298
377
  try {
299
378
  const targetSessionId = sessionId || this.sessionId;
@@ -302,8 +381,9 @@ export class HttpClient {
302
381
  body: JSON.stringify({
303
382
  args,
304
383
  command,
384
+ background,
305
385
  sessionId: targetSessionId,
306
- }),
386
+ } as ExecuteRequest),
307
387
  headers: {
308
388
  "Content-Type": "application/json",
309
389
  },
@@ -349,7 +429,8 @@ export class HttpClient {
349
429
  async executeStream(
350
430
  command: string,
351
431
  args: string[] = [],
352
- sessionId?: string
432
+ sessionId?: string,
433
+ background: boolean = false
353
434
  ): Promise<void> {
354
435
  try {
355
436
  const targetSessionId = sessionId || this.sessionId;
@@ -358,6 +439,7 @@ export class HttpClient {
358
439
  body: JSON.stringify({
359
440
  args,
360
441
  command,
442
+ background,
361
443
  sessionId: targetSessionId,
362
444
  }),
363
445
  headers: {
@@ -405,8 +487,7 @@ export class HttpClient {
405
487
  switch (event.type) {
406
488
  case "command_start":
407
489
  console.log(
408
- `[HTTP Client] Command started: ${
409
- event.command
490
+ `[HTTP Client] Command started: ${event.command
410
491
  } ${event.args?.join(" ")}`
411
492
  );
412
493
  this.options.onCommandStart?.(
@@ -487,7 +568,7 @@ export class HttpClient {
487
568
  repoUrl,
488
569
  sessionId: targetSessionId,
489
570
  targetDir,
490
- }),
571
+ } as GitCheckoutRequest),
491
572
  headers: {
492
573
  "Content-Type": "application/json",
493
574
  },
@@ -578,8 +659,7 @@ export class HttpClient {
578
659
  switch (event.type) {
579
660
  case "command_start":
580
661
  console.log(
581
- `[HTTP Client] Git checkout started: ${
582
- event.command
662
+ `[HTTP Client] Git checkout started: ${event.command
583
663
  } ${event.args?.join(" ")}`
584
664
  );
585
665
  this.options.onCommandStart?.(
@@ -658,7 +738,7 @@ export class HttpClient {
658
738
  path,
659
739
  recursive,
660
740
  sessionId: targetSessionId,
661
- }),
741
+ } as MkdirRequest),
662
742
  headers: {
663
743
  "Content-Type": "application/json",
664
744
  },
@@ -699,7 +779,7 @@ export class HttpClient {
699
779
  path,
700
780
  recursive,
701
781
  sessionId: targetSessionId,
702
- }),
782
+ } as MkdirRequest),
703
783
  headers: {
704
784
  "Content-Type": "application/json",
705
785
  },
@@ -745,8 +825,7 @@ export class HttpClient {
745
825
  switch (event.type) {
746
826
  case "command_start":
747
827
  console.log(
748
- `[HTTP Client] Mkdir started: ${
749
- event.command
828
+ `[HTTP Client] Mkdir started: ${event.command
750
829
  } ${event.args?.join(" ")}`
751
830
  );
752
831
  this.options.onCommandStart?.(
@@ -825,7 +904,7 @@ export class HttpClient {
825
904
  encoding,
826
905
  path,
827
906
  sessionId: targetSessionId,
828
- }),
907
+ } as WriteFileRequest),
829
908
  headers: {
830
909
  "Content-Type": "application/json",
831
910
  },
@@ -868,7 +947,7 @@ export class HttpClient {
868
947
  encoding,
869
948
  path,
870
949
  sessionId: targetSessionId,
871
- }),
950
+ } as WriteFileRequest),
872
951
  headers: {
873
952
  "Content-Type": "application/json",
874
953
  },
@@ -978,6 +1057,162 @@ export class HttpClient {
978
1057
  }
979
1058
  }
980
1059
 
1060
+ async readFile(
1061
+ path: string,
1062
+ encoding: string = "utf-8",
1063
+ sessionId?: string
1064
+ ): Promise<ReadFileResponse> {
1065
+ try {
1066
+ const targetSessionId = sessionId || this.sessionId;
1067
+
1068
+ const response = await this.doFetch(`/api/read`, {
1069
+ body: JSON.stringify({
1070
+ encoding,
1071
+ path,
1072
+ sessionId: targetSessionId,
1073
+ } as ReadFileRequest),
1074
+ headers: {
1075
+ "Content-Type": "application/json",
1076
+ },
1077
+ method: "POST",
1078
+ });
1079
+
1080
+ if (!response.ok) {
1081
+ const errorData = (await response.json().catch(() => ({}))) as {
1082
+ error?: string;
1083
+ };
1084
+ throw new Error(
1085
+ errorData.error || `HTTP error! status: ${response.status}`
1086
+ );
1087
+ }
1088
+
1089
+ const data: ReadFileResponse = await response.json();
1090
+ console.log(
1091
+ `[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
1092
+ );
1093
+
1094
+ return data;
1095
+ } catch (error) {
1096
+ console.error("[HTTP Client] Error reading file:", error);
1097
+ throw error;
1098
+ }
1099
+ }
1100
+
1101
+ async readFileStream(
1102
+ path: string,
1103
+ encoding: string = "utf-8",
1104
+ sessionId?: string
1105
+ ): Promise<void> {
1106
+ try {
1107
+ const targetSessionId = sessionId || this.sessionId;
1108
+
1109
+ const response = await this.doFetch(`/api/read/stream`, {
1110
+ body: JSON.stringify({
1111
+ encoding,
1112
+ path,
1113
+ sessionId: targetSessionId,
1114
+ } as ReadFileRequest),
1115
+ headers: {
1116
+ "Content-Type": "application/json",
1117
+ },
1118
+ method: "POST",
1119
+ });
1120
+
1121
+ if (!response.ok) {
1122
+ const errorData = (await response.json().catch(() => ({}))) as {
1123
+ error?: string;
1124
+ };
1125
+ throw new Error(
1126
+ errorData.error || `HTTP error! status: ${response.status}`
1127
+ );
1128
+ }
1129
+
1130
+ if (!response.body) {
1131
+ throw new Error("No response body for streaming request");
1132
+ }
1133
+
1134
+ const reader = response.body.getReader();
1135
+ const decoder = new TextDecoder();
1136
+
1137
+ try {
1138
+ while (true) {
1139
+ const { done, value } = await reader.read();
1140
+
1141
+ if (done) {
1142
+ break;
1143
+ }
1144
+
1145
+ const chunk = decoder.decode(value, { stream: true });
1146
+ const lines = chunk.split("\n");
1147
+
1148
+ for (const line of lines) {
1149
+ if (line.startsWith("data: ")) {
1150
+ try {
1151
+ const eventData = line.slice(6); // Remove 'data: ' prefix
1152
+ const event: StreamEvent = JSON.parse(eventData);
1153
+
1154
+ console.log(
1155
+ `[HTTP Client] Read file stream event: ${event.type}`
1156
+ );
1157
+ this.options.onStreamEvent?.(event);
1158
+
1159
+ switch (event.type) {
1160
+ case "command_start":
1161
+ console.log(
1162
+ `[HTTP Client] Read file started: ${event.path}`
1163
+ );
1164
+ this.options.onCommandStart?.("read", [path, encoding]);
1165
+ break;
1166
+
1167
+ case "command_complete":
1168
+ console.log(
1169
+ `[HTTP Client] Read file completed: ${event.path
1170
+ }, Success: ${event.success}, Content length: ${event.content?.length || 0
1171
+ }`
1172
+ );
1173
+ this.options.onCommandComplete?.(
1174
+ event.success!,
1175
+ 0,
1176
+ event.content || "",
1177
+ "",
1178
+ "read",
1179
+ [path, encoding]
1180
+ );
1181
+ break;
1182
+
1183
+ case "error":
1184
+ console.error(
1185
+ `[HTTP Client] Read file error: ${event.error}`
1186
+ );
1187
+ this.options.onError?.(event.error!, "read", [
1188
+ path,
1189
+ encoding,
1190
+ ]);
1191
+ break;
1192
+ }
1193
+ } catch (parseError) {
1194
+ console.warn(
1195
+ "[HTTP Client] Failed to parse read file stream event:",
1196
+ parseError
1197
+ );
1198
+ }
1199
+ }
1200
+ }
1201
+ }
1202
+ } finally {
1203
+ reader.releaseLock();
1204
+ }
1205
+ } catch (error) {
1206
+ console.error("[HTTP Client] Error in streaming read file:", error);
1207
+ this.options.onError?.(
1208
+ error instanceof Error ? error.message : "Unknown error",
1209
+ "read",
1210
+ [path, encoding]
1211
+ );
1212
+ throw error;
1213
+ }
1214
+ }
1215
+
981
1216
  async deleteFile(
982
1217
  path: string,
983
1218
  sessionId?: string
@@ -989,7 +1224,7 @@ export class HttpClient {
989
1224
  body: JSON.stringify({
990
1225
  path,
991
1226
  sessionId: targetSessionId,
992
- }),
1227
+ } as DeleteFileRequest),
993
1228
  headers: {
994
1229
  "Content-Type": "application/json",
995
1230
  },
@@ -1025,7 +1260,7 @@ export class HttpClient {
1025
1260
  body: JSON.stringify({
1026
1261
  path,
1027
1262
  sessionId: targetSessionId,
1028
- }),
1263
+ } as DeleteFileRequest),
1029
1264
  headers: {
1030
1265
  "Content-Type": "application/json",
1031
1266
  },
@@ -1135,7 +1370,7 @@ export class HttpClient {
1135
1370
  newPath,
1136
1371
  oldPath,
1137
1372
  sessionId: targetSessionId,
1138
- }),
1373
+ } as RenameFileRequest),
1139
1374
  headers: {
1140
1375
  "Content-Type": "application/json",
1141
1376
  },
@@ -1176,7 +1411,7 @@ export class HttpClient {
1176
1411
  newPath,
1177
1412
  oldPath,
1178
1413
  sessionId: targetSessionId,
1179
- }),
1414
+ } as RenameFileRequest),
1180
1415
  headers: {
1181
1416
  "Content-Type": "application/json",
1182
1417
  },
@@ -1289,7 +1524,7 @@ export class HttpClient {
1289
1524
  destinationPath,
1290
1525
  sessionId: targetSessionId,
1291
1526
  sourcePath,
1292
- }),
1527
+ } as MoveFileRequest),
1293
1528
  headers: {
1294
1529
  "Content-Type": "application/json",
1295
1530
  },
@@ -1330,7 +1565,7 @@ export class HttpClient {
1330
1565
  destinationPath,
1331
1566
  sessionId: targetSessionId,
1332
1567
  sourcePath,
1333
- }),
1568
+ } as MoveFileRequest),
1334
1569
  headers: {
1335
1570
  "Content-Type": "application/json",
1336
1571
  },
@@ -1433,6 +1668,104 @@ export class HttpClient {
1433
1668
  }
1434
1669
  }
1435
1670
 
1671
+ async exposePort(port: number, name?: string): Promise<ExposePortResponse> {
1672
+ try {
1673
+ const response = await this.doFetch(`/api/expose-port`, {
1674
+ body: JSON.stringify({
1675
+ port,
1676
+ name,
1677
+ }),
1678
+ headers: {
1679
+ "Content-Type": "application/json",
1680
+ },
1681
+ method: "POST",
1682
+ });
1683
+
1684
+ if (!response.ok) {
1685
+ const errorData = (await response.json().catch(() => ({}))) as {
1686
+ error?: string;
1687
+ };
1688
+ console.log(errorData);
1689
+ throw new Error(
1690
+ errorData.error || `HTTP error! status: ${response.status}`
1691
+ );
1692
+ }
1693
+
1694
+ const data: ExposePortResponse = await response.json();
1695
+ console.log(
1696
+ `[HTTP Client] Port exposed: ${port}${name ? ` (${name})` : ""}, Success: ${data.success}`
1697
+ );
1698
+
1699
+ return data;
1700
+ } catch (error) {
1701
+ console.error("[HTTP Client] Error exposing port:", error);
1702
+ throw error;
1703
+ }
1704
+ }
1705
+
1706
+ async unexposePort(port: number): Promise<UnexposePortResponse> {
1707
+ try {
1708
+ const response = await this.doFetch(`/api/unexpose-port`, {
1709
+ body: JSON.stringify({
1710
+ port,
1711
+ }),
1712
+ headers: {
1713
+ "Content-Type": "application/json",
1714
+ },
1715
+ method: "DELETE",
1716
+ });
1717
+
1718
+ if (!response.ok) {
1719
+ const errorData = (await response.json().catch(() => ({}))) as {
1720
+ error?: string;
1721
+ };
1722
+ throw new Error(
1723
+ errorData.error || `HTTP error! status: ${response.status}`
1724
+ );
1725
+ }
1726
+
1727
+ const data: UnexposePortResponse = await response.json();
1728
+ console.log(
1729
+ `[HTTP Client] Port unexposed: ${port}, Success: ${data.success}`
1730
+ );
1731
+
1732
+ return data;
1733
+ } catch (error) {
1734
+ console.error("[HTTP Client] Error unexposing port:", error);
1735
+ throw error;
1736
+ }
1737
+ }
1738
+
1739
+ async getExposedPorts(): Promise<GetExposedPortsResponse> {
1740
+ try {
1741
+ const response = await this.doFetch(`/api/exposed-ports`, {
1742
+ headers: {
1743
+ "Content-Type": "application/json",
1744
+ },
1745
+ method: "GET",
1746
+ });
1747
+
1748
+ if (!response.ok) {
1749
+ const errorData = (await response.json().catch(() => ({}))) as {
1750
+ error?: string;
1751
+ };
1752
+ throw new Error(
1753
+ errorData.error || `HTTP error! status: ${response.status}`
1754
+ );
1755
+ }
1756
+
1757
+ const data: GetExposedPortsResponse = await response.json();
1758
+ console.log(
1759
+ `[HTTP Client] Got ${data.count} exposed ports`
1760
+ );
1761
+
1762
+ return data;
1763
+ } catch (error) {
1764
+ console.error("[HTTP Client] Error getting exposed ports:", error);
1765
+ throw error;
1766
+ }
1767
+ }
1768
+
1436
1769
  async ping(): Promise<string> {
1437
1770
  try {
1438
1771
  const response = await this.doFetch(`/api/ping`, {
@@ -1629,6 +1962,38 @@ export async function quickWriteFileStream(
1629
1962
  }
1630
1963
  }
1631
1964
 
1965
+ // Convenience function for quick file reading
1966
+ export async function quickReadFile(
1967
+ path: string,
1968
+ encoding: string = "utf-8",
1969
+ options?: HttpClientOptions
1970
+ ): Promise<ReadFileResponse> {
1971
+ const client = createClient(options);
1972
+ await client.createSession();
1973
+
1974
+ try {
1975
+ return await client.readFile(path, encoding);
1976
+ } finally {
1977
+ client.clearSession();
1978
+ }
1979
+ }
1980
+
1981
+ // Convenience function for quick streaming file reading
1982
+ export async function quickReadFileStream(
1983
+ path: string,
1984
+ encoding: string = "utf-8",
1985
+ options?: HttpClientOptions
1986
+ ): Promise<void> {
1987
+ const client = createClient(options);
1988
+ await client.createSession();
1989
+
1990
+ try {
1991
+ await client.readFileStream(path, encoding);
1992
+ } finally {
1993
+ client.clearSession();
1994
+ }
1995
+ }
1996
+
1632
1997
  // Convenience function for quick file deletion
1633
1998
  export async function quickDeleteFile(
1634
1999
  path: string,